feat: Allow to add annotations when che server create namspace like labels (#57)

* Allow to add annotations when che server create namspace like labels

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Rename che.infra.kubernetes.namespace.annotate to che.infra.kubernetes.namespace.annotation

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Remove quick fix on devfile (need dedicated PR)

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Replace placeHolders on namespace annotations

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Add tests to valide placeholders on annotation. Rename properties to che.infra.kubernetes.namespace.annotate

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Fix annotations placeholder on openshift project

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Try to fix test with add cleanup context on KeycloakProviderConfigFactoryTest

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* Add lenient method on test to avoid unnecessary stubbing error

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>

* clean test by remove unused stubs

Signed-off-by: disaster37 <linuxworkgroup@hotmail.com>
pull/72/head
disaster37 2021-08-03 11:31:36 +02:00 committed by GitHub
parent 629cc75716
commit 7bb2641d15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 383 additions and 29 deletions

View File

@ -312,6 +312,9 @@ che.infra.kubernetes.namespace.default=<username>-che
# Defines whether che-server should try to label the workspace namespaces.
che.infra.kubernetes.namespace.label=true
# Defines whether che-server should try to annotate the workspace namespaces.
che.infra.kubernetes.namespace.annotate=true
# List of labels to find {orch-namespace} that are used for {prod-short} Workspaces.
# They are used to:
# - find prepared {orch-namespace} for users in combination with `che.infra.kubernetes.namespace.annotations`.
@ -323,6 +326,9 @@ che.infra.kubernetes.namespace.labels=app.kubernetes.io/part-of=che.eclipse.org,
# {orch-namespace} that matches both `che.infra.kubernetes.namespace.labels` and `che.infra.kubernetes.namespace.annotations`
# will be preferentially used for User's workspaces.
# It's possible to use `<username>` placeholder to specify the {orch-namespace} to concrete user.
# They are used to:
# - find prepared {orch-namespace} for users in combination with `che.infra.kubernetes.namespace.labels`.
# - actively annotate {orch-namespace} with any workspace.
che.infra.kubernetes.namespace.annotations=che.eclipse.org/username=<username>
# Defines Kubernetes Service Account name which should be specified to be bound to all workspaces Pods.

View File

@ -132,13 +132,19 @@ public class KubernetesNamespace {
* namespace already exists or we create new one. If update labels operation fail due to lack of
* permission, we do not fail completely.
*
* <p>The method will try to annotate the namespace with provided `annotations`. It does not
* matter if the namespace already exists or we create new one. If update annotations operation
* fail due to lack of permission, we do not fail completely.
*
* @param canCreate defines what to do when the namespace is not found. The namespace is created
* when {@code true}, otherwise an exception is thrown.
* @param labels labels that should be set to the namespace
* @param annotations annotations that should be set to the namespace
* @throws InfrastructureException if any exception occurs during namespace preparation or if the
* namespace doesn't exist and {@code canCreate} is {@code false}.
*/
void prepare(boolean canCreate, Map<String, String> labels) throws InfrastructureException {
void prepare(boolean canCreate, Map<String, String> labels, Map<String, String> annotations)
throws InfrastructureException {
KubernetesClient client = clientFactory.create(workspaceId);
Namespace namespace = get(name, client);
@ -150,6 +156,7 @@ public class KubernetesNamespace {
namespace = create(name, client);
}
label(namespace, labels);
annotate(namespace, annotations);
}
/**
@ -204,6 +211,58 @@ public class KubernetesNamespace {
}
}
/**
* Applies given `ensureAnnotations` into given `namespace` and update the `namespace` in the
* Kubernetes.
*
* <p>If we do not have permissions to do so (code=403), this method does not throw any exception.
*
* @param namespace namespace to annotate
* @param ensureAnnotations these annotations should be applied on given `namespace`
* @throws InfrastructureException if something goes wrong with update, except lack of permissions
*/
protected void annotate(Namespace namespace, Map<String, String> ensureAnnotations)
throws InfrastructureException {
if (ensureAnnotations.isEmpty()) {
return;
}
Map<String, String> currentAnnotations = namespace.getMetadata().getAnnotations();
Map<String, String> newAnnotations =
currentAnnotations != null ? new HashMap<>(currentAnnotations) : new HashMap<>();
if (newAnnotations.entrySet().containsAll(ensureAnnotations.entrySet())) {
LOG.debug(
"Nothing to do, namespace [{}] already has all required annotations.",
namespace.getMetadata().getName());
return;
}
try {
// update the namespace with new annotations
cheSAClientFactory
.create()
.namespaces()
.createOrReplace(
new NamespaceBuilder(namespace)
.editMetadata()
.addToAnnotations(ensureAnnotations)
.endMetadata()
.build());
} catch (KubernetesClientException kce) {
if (kce.getCode() == 403) {
LOG.warn(
"Can't annotate the namespace due to lack of permissions. Grant cluster-wide permissions "
+ "to `get` and `update` the `namespaces` to the `che` service account "
+ "(Che operator might have already prepared a cluster role called "
+ "`che-namespace-editor` for this, depending on its configuration). "
+ "Alternatively, consider disabling the feature by setting "
+ "`che.infra.kubernetes.namepsace.annotate` to `false`.");
return;
}
throw new InfrastructureException(kce);
}
}
/**
* Deletes the namespace. Deleting a non-existent namespace is not an error as is not an attempt
* to delete a namespace that is already being deleted.

View File

@ -93,6 +93,7 @@ public class KubernetesNamespaceFactory {
private final String defaultNamespaceName;
protected final boolean labelNamespaces;
protected final boolean annotateNamespaces;
protected final Map<String, String> namespaceLabels;
protected final Map<String, String> namespaceAnnotations;
@ -112,6 +113,7 @@ public class KubernetesNamespaceFactory {
@Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName,
@Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed,
@Named("che.infra.kubernetes.namespace.label") boolean labelNamespaces,
@Named("che.infra.kubernetes.namespace.annotate") boolean annotateNamespaces,
@Named("che.infra.kubernetes.namespace.labels") String namespaceLabels,
@Named("che.infra.kubernetes.namespace.annotations") String namespaceAnnotations,
KubernetesClientFactory clientFactory,
@ -129,6 +131,7 @@ public class KubernetesNamespaceFactory {
this.preferenceManager = preferenceManager;
this.sharedPool = sharedPool;
this.labelNamespaces = labelNamespaces;
this.annotateNamespaces = annotateNamespaces;
//noinspection UnstableApiUsage
Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("=");
@ -327,7 +330,15 @@ public class KubernetesNamespaceFactory {
public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
KubernetesNamespace namespace = get(identity);
namespace.prepare(canCreateNamespace(identity), labelNamespaces ? namespaceLabels : emptyMap());
NamespaceResolutionContext resolutionCtx =
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject());
Map<String, String> namespaceAnnotationsEvaluated =
evaluateAnnotationPlaceholders(resolutionCtx);
namespace.prepare(
canCreateNamespace(identity),
labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
if (!isNullOrEmpty(serviceAccountName)) {
KubernetesWorkspaceServiceAccount workspaceServiceAccount =

View File

@ -165,6 +165,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -192,6 +193,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -217,6 +219,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -239,6 +242,7 @@ public class KubernetesNamespaceFactoryTest {
null,
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -289,6 +293,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -332,6 +337,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -362,6 +368,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -395,6 +402,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -423,6 +431,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -454,6 +463,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -479,6 +489,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -509,6 +520,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -526,7 +538,7 @@ public class KubernetesNamespaceFactoryTest {
// then
assertEquals(toReturnNamespace, namespace);
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnNamespace).prepare(eq(false), any());
verify(toReturnNamespace).prepare(eq(false), any(), any());
}
@Test
@ -540,6 +552,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
false,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -558,7 +571,7 @@ public class KubernetesNamespaceFactoryTest {
// then
assertEquals(toReturnNamespace, namespace);
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnNamespace).prepare(eq(false), any());
verify(toReturnNamespace).prepare(eq(false), any(), any());
}
@Test
@ -573,6 +586,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -610,6 +624,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -679,6 +694,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -735,6 +751,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -754,6 +771,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -777,6 +795,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -802,6 +821,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -832,6 +852,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>-<username>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -863,6 +884,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>-<username>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -894,6 +916,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -923,6 +946,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -973,6 +997,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -1010,6 +1035,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
"try_placeholder_here=<username>",
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -1023,6 +1049,40 @@ public class KubernetesNamespaceFactoryTest {
verify(namespaceOperation).withLabels(Map.of("try_placeholder_here", "<username>"));
}
@Test
public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException {
// given
namespaceFactory =
spy(
new KubernetesNamespaceFactory(
"",
"",
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
"try_placeholder_here=<username>",
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
// when
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che");
KubernetesNamespace namespace = namespaceFactory.getOrCreate(identity);
// then
assertEquals(toReturnNamespace, namespace);
verify(toReturnNamespace)
.prepare(eq(false), any(), eq(Map.of("try_placeholder_here", "jondoe")));
}
@Test(dataProvider = "invalidUsernames")
public void normalizeTest(String raw, String expected) {
namespaceFactory =
@ -1032,6 +1092,7 @@ public class KubernetesNamespaceFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
@ -1051,6 +1112,7 @@ public class KubernetesNamespaceFactoryTest {
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,

View File

@ -12,6 +12,7 @@
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace;
import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND;
import static java.util.Collections.emptyMap;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@ -40,7 +41,6 @@ import io.fabric8.kubernetes.client.WatcherException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
@ -116,7 +116,7 @@ public class KubernetesNamespaceTest {
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
namespace.prepare(true, Map.of());
namespace.prepare(true, Map.of(), Map.of());
// then
verify(namespaceOperation, never()).create(any(Namespace.class));
@ -132,7 +132,7 @@ public class KubernetesNamespaceTest {
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
namespace.prepare(true, Map.of());
namespace.prepare(true, Map.of(), Map.of());
// then
ArgumentCaptor<Namespace> captor = ArgumentCaptor.forClass(Namespace.class);
@ -149,7 +149,7 @@ public class KubernetesNamespaceTest {
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
namespace.prepare(false, Map.of());
namespace.prepare(false, Map.of(), Map.of());
// then
// exception is thrown
@ -196,7 +196,7 @@ public class KubernetesNamespaceTest {
doThrow(KubernetesClientException.class).when(kubernetesClient).serviceAccounts();
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(false, Map.of());
.prepare(false, Map.of(), Map.of());
}
@Test(expectedExceptions = InfrastructureException.class)
@ -207,7 +207,7 @@ public class KubernetesNamespaceTest {
when(serviceAccountResource.get()).thenReturn(null);
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(false, Map.of());
.prepare(false, Map.of(), Map.of());
}
@Test(expectedExceptions = InfrastructureException.class)
@ -226,7 +226,7 @@ public class KubernetesNamespaceTest {
.watch(any());
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(false, Map.of());
.prepare(false, Map.of(), Map.of());
}
@Test
@ -245,7 +245,7 @@ public class KubernetesNamespaceTest {
.watch(any());
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID)
.prepare(true, Map.of());
.prepare(true, Map.of(), Map.of());
verify(serviceAccountResource).get();
verify(serviceAccountResource).watch(any());
@ -318,7 +318,7 @@ public class KubernetesNamespaceTest {
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
// when
kubernetesNamespace.prepare(true, labels);
kubernetesNamespace.prepare(true, labels, emptyMap());
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
@ -349,7 +349,7 @@ public class KubernetesNamespaceTest {
.createOrReplace(any(Namespace.class));
// when
kubernetesNamespace.prepare(true, labels);
kubernetesNamespace.prepare(true, labels, emptyMap());
// then
assertTrue(namespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
@ -377,7 +377,7 @@ public class KubernetesNamespaceTest {
.createOrReplace(any(Namespace.class));
// when
kubernetesNamespace.prepare(true, Collections.emptyMap());
kubernetesNamespace.prepare(true, emptyMap(), emptyMap());
// then
assertTrue(
@ -385,6 +385,80 @@ public class KubernetesNamespaceTest {
verify(nonNamespaceOperation, never()).createOrReplace(any());
}
public void testAnnotateNamespace() throws InfrastructureException {
// given
prepareNamespace(NAMESPACE);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
ArgumentCaptor<Namespace> namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class);
doAnswer(a -> a.getArgument(0))
.when(nonNamespaceOperation)
.createOrReplace(namespaceArgumentCaptor.capture());
Map<String, String> annotations = Map.of("annotate.with.this", "yes", "and.this", "of courese");
// when
kubernetesNamespace.prepare(true, emptyMap(), annotations);
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
assertTrue(
updatedNamespace
.getMetadata()
.getAnnotations()
.entrySet()
.containsAll(annotations.entrySet()));
assertEquals(updatedNamespace.getMetadata().getName(), NAMESPACE);
}
@Test
public void testDontTryToAnnotateNamespaceIfAlreadyAnnoted() throws InfrastructureException {
// given
Map<String, String> annotations =
Map.of("annotation.with.this", "yes", "and.this", "of courese");
Namespace namespace = prepareNamespace(NAMESPACE);
namespace.getMetadata().setAnnotations(annotations);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
kubernetesNamespace.prepare(true, emptyMap(), annotations);
// then
assertTrue(
namespace.getMetadata().getAnnotations().entrySet().containsAll(annotations.entrySet()));
}
@Test
public void testDontTryToAnnotateNamespaceIfNoNamespacesProvided()
throws InfrastructureException {
// given
Map<String, String> existingAnnotations = Map.of("some", "annotations");
Namespace namespace = prepareNamespace(NAMESPACE);
namespace.getMetadata().setAnnotations(existingAnnotations);
KubernetesNamespace kubernetesNamespace =
new KubernetesNamespace(clientFactory, cheClientFactory, executor, NAMESPACE, WORKSPACE_ID);
// when
kubernetesNamespace.prepare(true, emptyMap(), emptyMap());
// then
assertTrue(
namespace
.getMetadata()
.getAnnotations()
.entrySet()
.containsAll(existingAnnotations.entrySet()));
}
@Test
public void testDoNotFailWhenNoPermissionsToUpdateNamespace() throws InfrastructureException {
// given
@ -407,7 +481,7 @@ public class KubernetesNamespaceTest {
.createOrReplace(namespaceArgumentCaptor.capture());
// when
kubernetesNamespace.prepare(true, labels);
kubernetesNamespace.prepare(true, labels, emptyMap());
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
@ -437,7 +511,7 @@ public class KubernetesNamespaceTest {
.createOrReplace(any(Namespace.class));
// when
kubernetesNamespace.prepare(true, labels);
kubernetesNamespace.prepare(true, labels, emptyMap());
// then
verify(nonNamespaceOperation).createOrReplace(namespace);

View File

@ -110,7 +110,11 @@ public class OpenShiftProject extends KubernetesNamespace {
* @throws InfrastructureException if any exception occurs during project preparation or if the
* project doesn't exist and {@code canCreate} is {@code false}.
*/
void prepare(boolean canCreate, boolean initWithCheServerSa, Map<String, String> labels)
void prepare(
boolean canCreate,
boolean initWithCheServerSa,
Map<String, String> labels,
Map<String, String> annotations)
throws InfrastructureException {
String workspaceId = getWorkspaceId();
String projectName = getName();
@ -153,6 +157,7 @@ public class OpenShiftProject extends KubernetesNamespace {
}
}
label(osClient.namespaces().withName(projectName).get(), labels);
annotate(osClient.namespaces().withName(projectName).get(), annotations);
}
/**

View File

@ -36,6 +36,7 @@ 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.commons.annotation.Nullable;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
@ -71,6 +72,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
@Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName,
@Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed,
@Named("che.infra.kubernetes.namespace.label") boolean labelProjects,
@Named("che.infra.kubernetes.namespace.annotate") boolean annotateProjects,
@Named("che.infra.kubernetes.namespace.labels") String projectLabels,
@Named("che.infra.kubernetes.namespace.annotations") String projectAnnotations,
@Named("che.infra.openshift.project.init_with_server_sa") boolean initWithCheServerSa,
@ -89,6 +91,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
defaultNamespaceName,
namespaceCreationAllowed,
labelProjects,
annotateProjects,
projectLabels,
projectAnnotations,
clientFactory,
@ -106,10 +109,16 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
OpenShiftProject osProject = get(identity);
NamespaceResolutionContext resolutionCtx =
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject());
Map<String, String> namespaceAnnotationsEvaluated =
evaluateAnnotationPlaceholders(resolutionCtx);
osProject.prepare(
canCreateNamespace(identity),
initWithCheServerSa && !isNullOrEmpty(oAuthIdentityProvider),
labelNamespaces ? namespaceLabels : emptyMap());
labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
if (!isNullOrEmpty(getServiceAccountName())) {
OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount =

View File

@ -44,6 +44,7 @@ import org.eclipse.che.multiuser.keycloak.server.KeycloakSettings;
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse;
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;
@ -134,6 +135,11 @@ public class KeycloakProviderConfigFactoryTest {
defaultConfig = new io.fabric8.kubernetes.client.ConfigBuilder().build();
}
@AfterMethod
public void cleanup() {
EnvironmentContext.reset();
}
@Test
public void testFallbackToDefaultConfigWhenProvideIsNull() throws Exception {
configBuilder =

View File

@ -152,6 +152,7 @@ public class OpenShiftProjectFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -184,6 +185,7 @@ public class OpenShiftProjectFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -216,6 +218,7 @@ public class OpenShiftProjectFactoryTest {
null,
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -253,6 +256,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -291,6 +295,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -325,6 +330,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -369,6 +375,7 @@ public class OpenShiftProjectFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -407,6 +414,7 @@ public class OpenShiftProjectFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -445,6 +453,7 @@ public class OpenShiftProjectFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -473,6 +482,7 @@ public class OpenShiftProjectFactoryTest {
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -506,6 +516,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -528,7 +539,7 @@ public class OpenShiftProjectFactoryTest {
// then
assertEquals(toReturnProject, project);
verify(projectFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnProject).prepare(eq(false), eq(false), any());
verify(toReturnProject).prepare(eq(false), eq(false), any(), any());
}
@Test
@ -543,6 +554,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -582,6 +594,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -623,6 +636,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -676,6 +690,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
@ -710,6 +725,7 @@ public class OpenShiftProjectFactoryTest {
"<userid>-che",
true,
true,
true,
"try_placeholder_here=<username>",
NAMESPACE_ANNOTATIONS,
true,
@ -727,6 +743,44 @@ public class OpenShiftProjectFactoryTest {
verify(projectOperation).withLabels(Map.of("try_placeholder_here", "<username>"));
}
@Test
public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException {
// given
projectFactory =
spy(
new OpenShiftProjectFactory(
"",
null,
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
"try_placeholder_here=<username>",
true,
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager,
preferenceManager,
pool,
NO_OAUTH_IDENTITY_PROVIDER));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
// when
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che");
OpenShiftProject project = projectFactory.getOrCreate(identity);
// then
assertEquals(toReturnProject, project);
verify(toReturnProject)
.prepare(eq(false), eq(false), any(), eq(Map.of("try_placeholder_here", "jondoe")));
}
private void prepareNamespaceToBeFoundByName(String name, Project project) throws Exception {
@SuppressWarnings("unchecked")
Resource<Project> getProjectByNameOperation = mock(Resource.class);

View File

@ -156,7 +156,7 @@ public class OpenShiftProjectTest {
WORKSPACE_ID);
// when
project.prepare(true, true, Map.of());
project.prepare(true, true, Map.of(), Map.of());
// then
verify(metadataNested, never()).withName(PROJECT_NAME);
@ -179,7 +179,7 @@ public class OpenShiftProjectTest {
WORKSPACE_ID);
// when
openShiftProject.prepare(true, false, Map.of());
openShiftProject.prepare(true, false, Map.of(), Map.of());
// then
ArgumentCaptor<ProjectRequest> captor = ArgumentCaptor.forClass(ProjectRequest.class);
@ -210,7 +210,7 @@ public class OpenShiftProjectTest {
when(openShiftClient.currentUser())
.thenReturn(new UserBuilder().withNewMetadata().withName("user").endMetadata().build());
// when
openShiftProject.prepare(true, true, Map.of());
openShiftProject.prepare(true, true, Map.of(), Map.of());
// then
ArgumentCaptor<ProjectRequest> captor = ArgumentCaptor.forClass(ProjectRequest.class);
@ -247,7 +247,7 @@ public class OpenShiftProjectTest {
when(openShiftClient.currentUser())
.thenReturn(new UserBuilder().withNewMetadata().withName("jdoe").endMetadata().build());
// when
openShiftProject.prepare(true, true, Map.of());
openShiftProject.prepare(true, true, Map.of(), Map.of());
// then
ArgumentCaptor<RoleBinding> roleBindingArgumentCaptor =
@ -275,7 +275,7 @@ public class OpenShiftProjectTest {
WORKSPACE_ID);
// when
project.prepare(false, true, Map.of());
project.prepare(false, true, Map.of(), Map.of());
// then
// exception is thrown
@ -406,7 +406,7 @@ public class OpenShiftProjectTest {
Map<String, String> labels = Map.of("label.with.this", "yes", "and.this", "of courese");
// when
openShiftProject.prepare(true, true, labels);
openShiftProject.prepare(true, true, labels, Map.of());
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
@ -444,13 +444,81 @@ public class OpenShiftProjectTest {
.createOrReplace(any(Namespace.class));
// when
openShiftProject.prepare(true, true, labels);
openShiftProject.prepare(true, true, labels, Map.of());
// then
assertTrue(namespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet()));
verify(nonNamespaceOperation, never()).createOrReplace(any());
}
@Test
public void testAnnotateNamespace() throws InfrastructureException {
// given
prepareProject(PROJECT_NAME);
prepareNamespaceGet(PROJECT_NAME);
OpenShiftProject openShiftProject =
new OpenShiftProject(
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
executor,
PROJECT_NAME,
WORKSPACE_ID);
KubernetesClient cheKubeClient = mock(KubernetesClient.class);
doReturn(cheKubeClient).when(cheClientFactory).create();
NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class);
doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces();
ArgumentCaptor<Namespace> namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class);
doAnswer(a -> a.getArgument(0))
.when(nonNamespaceOperation)
.createOrReplace(namespaceArgumentCaptor.capture());
Map<String, String> annotations =
Map.of("annotation.with.this", "yes", "and.this", "of courese");
// when
openShiftProject.prepare(true, true, Map.of(), annotations);
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
assertTrue(
updatedNamespace
.getMetadata()
.getAnnotations()
.entrySet()
.containsAll(annotations.entrySet()));
assertEquals(updatedNamespace.getMetadata().getName(), PROJECT_NAME);
}
@Test
public void testDontTryToAnnotateNamespaceIfAlreadyAnnotated() throws InfrastructureException {
// given
Map<String, String> annotations =
Map.of("annotation.with.this", "yes", "and.this", "of courese");
prepareProject(PROJECT_NAME);
Namespace namespace = prepareNamespaceGet(PROJECT_NAME);
namespace.getMetadata().setAnnotations(annotations);
OpenShiftProject openShiftProject =
new OpenShiftProject(
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
executor,
PROJECT_NAME,
WORKSPACE_ID);
// when
openShiftProject.prepare(true, true, Map.of(), annotations);
// then
assertTrue(
namespace.getMetadata().getAnnotations().entrySet().containsAll(annotations.entrySet()));
}
@Test
public void testDoNotFailWhenNoPermissionsToUpdateNamespace() throws InfrastructureException {
// given
@ -480,7 +548,7 @@ public class OpenShiftProjectTest {
.createOrReplace(namespaceArgumentCaptor.capture());
// when
openShiftProject.prepare(true, true, labels);
openShiftProject.prepare(true, true, labels, Map.of());
// then
Namespace updatedNamespace = namespaceArgumentCaptor.getValue();
@ -517,7 +585,7 @@ public class OpenShiftProjectTest {
.createOrReplace(any(Namespace.class));
// when
openShiftProject.prepare(true, true, labels);
openShiftProject.prepare(true, true, labels, Map.of());
// then
verify(nonNamespaceOperation).createOrReplace(namespace);