Skip to content

Commit

Permalink
Provision git configuration into each container
Browse files Browse the repository at this point in the history
Signed-off-by: Vlad Zhukovskyi <[email protected]>
  • Loading branch information
vzhukovs committed Sep 3, 2019
1 parent 16b803d commit 6b4bd00
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 3 deletions.
4 changes: 4 additions & 0 deletions infrastructures/kubernetes/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-system</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-user</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitUserProfileProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.InstallerServersPortProvisioner;
Expand Down Expand Up @@ -73,6 +74,7 @@ class KubernetesEnvironmentProvisionerImpl
private final ServiceAccountProvisioner serviceAccountProvisioner;
private final CertificateProvisioner certificateProvisioner;
private final VcsSshKeysProvisioner vcsSshKeysProvisioner;
private final GitUserProfileProvisioner gitUserProfileProvisioner;

@Inject
public KubernetesEnvironmentProvisionerImpl(
Expand All @@ -92,7 +94,8 @@ public KubernetesEnvironmentProvisionerImpl(
ProxySettingsProvisioner proxySettingsProvisioner,
ServiceAccountProvisioner serviceAccountProvisioner,
CertificateProvisioner certificateProvisioner,
VcsSshKeysProvisioner vcsSshKeysProvisioner) {
VcsSshKeysProvisioner vcsSshKeysProvisioner,
GitUserProfileProvisioner gitUserProfileProvisioner) {
this.pvcEnabled = pvcEnabled;
this.volumesStrategy = volumesStrategy;
this.uniqueNamesProvisioner = uniqueNamesProvisioner;
Expand All @@ -110,6 +113,7 @@ public KubernetesEnvironmentProvisionerImpl(
this.serviceAccountProvisioner = serviceAccountProvisioner;
this.certificateProvisioner = certificateProvisioner;
this.vcsSshKeysProvisioner = vcsSshKeysProvisioner;
this.gitUserProfileProvisioner = gitUserProfileProvisioner;
}

@Traced
Expand Down Expand Up @@ -149,6 +153,7 @@ public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)
serviceAccountProvisioner.provision(k8sEnv, identity);
certificateProvisioner.provision(k8sEnv, identity);
vcsSshKeysProvisioner.provision(k8sEnv, identity);
gitUserProfileProvisioner.provision(k8sEnv, identity);
LOG.debug("Provisioning Kubernetes environment done for workspace '{}'", workspaceId);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Collections.singletonMap;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.user.server.PreferenceManager;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;

@Singleton
public class GitUserProfileProvisioner implements ConfigurationProvisioner<KubernetesEnvironment> {

private final String GIT_CONFIG_MAP_NAME_SUFFIX = "-gitconfig";

private static final String GIT_BASE_CONFIG_PATH = "/etc/";
private static final String GIT_CONFIG = "gitconfig";
private static final String GIT_CONFIG_PATH = GIT_BASE_CONFIG_PATH + GIT_CONFIG;
private static final String PREFERENCES_KEY_FILTER = "theia-user-preferences";
private static final String GIT_USER_NAME_PROPERTY = "git.user.name";
private static final String GIT_USER_EMAIL_PROPERTY = "git.user.email";
private static final String CONFIG_MAP_VOLUME_NAME = "gitconfigvolume";

private PreferenceManager preferenceManager;

@Inject
public GitUserProfileProvisioner(PreferenceManager preferenceManager) {
this.preferenceManager = preferenceManager;
}

@Override
public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)
throws InfrastructureException {
getPreferenceValue(PREFERENCES_KEY_FILTER)
.ifPresent(
preferenceJsonValue -> {
Map<String, String> theiaPreferences = getMapFromJsonObject(preferenceJsonValue);

getGlobalGitConfigFileContent(
theiaPreferences.get(GIT_USER_NAME_PROPERTY),
theiaPreferences.get(GIT_USER_EMAIL_PROPERTY))
.ifPresent(
gitConfigFileContent -> {
String gitConfigMapName =
identity.getWorkspaceId() + GIT_CONFIG_MAP_NAME_SUFFIX;

doProvisionGlobalGitConfig(gitConfigMapName, gitConfigFileContent, k8sEnv);
});
});
}

private Map<String, String> getMapFromJsonObject(String json) {
Type stringMapType = new TypeToken<Map<String, String>>() {}.getType();

return new Gson().fromJson(json, stringMapType);
}

private Optional<String> getPreferenceValue(String keyFilter) throws InfrastructureException {
try {
String userId = EnvironmentContext.getCurrent().getSubject().getUserId();
Map<String, String> preferencesMap = preferenceManager.find(userId, keyFilter);

return ofNullable(preferencesMap.get(keyFilter));
} catch (ServerException e) {
throw new InfrastructureException(e);
}
}

private Optional<String> getGlobalGitConfigFileContent(String userName, String userEmail) {
if (isNullOrEmpty(userName) && isNullOrEmpty(userEmail)) {
return empty();
}

StringBuilder config = new StringBuilder();
config.append("[user]").append('\n');

if (userName != null) {
config.append('\t').append("name = ").append(userName).append('\n');
}

if (userEmail != null) {
config.append('\t').append("email = ").append(userEmail).append('\n');
}

return of(config.toString());
}

private void doProvisionGlobalGitConfig(
String gitConfigMapName, String gitConfig, KubernetesEnvironment k8sEnv) {
Map<String, String> gitConfigData = singletonMap(GIT_CONFIG, gitConfig);
ConfigMap configMap =
new ConfigMapBuilder()
.withNewMetadata()
.withName(gitConfigMapName)
.endMetadata()
.withData(gitConfigData)
.build();

k8sEnv.getConfigMaps().put(configMap.getMetadata().getName(), configMap);
k8sEnv.getPodsData().values().forEach(p -> mountConfigFile(p.getSpec(), gitConfigMapName));
}

private void mountConfigFile(PodSpec podSpec, String gitConfigMapName) {
podSpec
.getVolumes()
.add(
new VolumeBuilder()
.withName(CONFIG_MAP_VOLUME_NAME)
.withConfigMap(
new ConfigMapVolumeSourceBuilder().withName(gitConfigMapName).build())
.build());

List<Container> containers = podSpec.getContainers();
containers.forEach(
container -> {
VolumeMount volumeMount =
new VolumeMountBuilder()
.withName(CONFIG_MAP_VOLUME_NAME)
.withMountPath(GIT_CONFIG_PATH)
.withSubPath(GIT_CONFIG)
.withReadOnly(false)
.withNewReadOnly(false)
.build();
container.getVolumeMounts().add(volumeMount);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitUserProfileProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.InstallerServersPortProvisioner;
Expand Down Expand Up @@ -66,6 +67,7 @@ public class KubernetesEnvironmentProvisionerTest {
@Mock private ServiceAccountProvisioner serviceAccountProvisioner;
@Mock private CertificateProvisioner certificateProvisioner;
@Mock private VcsSshKeysProvisioner vcsSshKeysProvisioner;
@Mock private GitUserProfileProvisioner gitUserProfileProvisioner;

private KubernetesEnvironmentProvisioner<KubernetesEnvironment> k8sInfraProvisioner;

Expand All @@ -91,7 +93,8 @@ public void setUp() {
proxySettingsProvisioner,
serviceAccountProvisioner,
certificateProvisioner,
vcsSshKeysProvisioner);
vcsSshKeysProvisioner,
gitUserProfileProvisioner);
provisionOrder =
inOrder(
installerServersPortProvisioner,
Expand All @@ -108,7 +111,8 @@ public void setUp() {
imagePullSecretProvisioner,
proxySettingsProvisioner,
serviceAccountProvisioner,
certificateProvisioner);
certificateProvisioner,
gitUserProfileProvisioner);
}

@Test
Expand Down Expand Up @@ -136,6 +140,7 @@ public void performsOrderedProvisioning() throws Exception {
provisionOrder.verify(proxySettingsProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(serviceAccountProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(certificateProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(gitUserProfileProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verifyNoMoreInteractions();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import io.fabric8.kubernetes.api.model.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.user.server.PreferenceManager;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
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 GitUserProfileProvisionerTest {

private KubernetesEnvironment k8sEnv;

@Mock private RuntimeIdentity runtimeIdentity;

@Mock private Pod pod;

@Mock private PodSpec podSpec;

@Mock private Container container;

@Mock private PreferenceManager preferenceManager;

private GitUserProfileProvisioner gitUserProfileProvisioner;

@BeforeMethod
public void setup() {
k8sEnv = KubernetesEnvironment.builder().build();
ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build();
when(pod.getMetadata()).thenReturn(podMeta);
when(pod.getSpec()).thenReturn(podSpec);
k8sEnv.addPod(pod);
gitUserProfileProvisioner = new GitUserProfileProvisioner(preferenceManager);

Subject subject = new SubjectImpl(null, "id", null, false);
EnvironmentContext environmentContext = new EnvironmentContext();
environmentContext.setSubject(subject);
EnvironmentContext.setCurrent(environmentContext);
}

@Test
public void testShouldDoNothingWhenGitUserNameAndEmailIsNotConfigured() throws Exception {
when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(emptyMap());
gitUserProfileProvisioner.provision(k8sEnv, runtimeIdentity);

verifyZeroInteractions(runtimeIdentity);
}

@Test
public void testShouldDoNothingWhenGitPreferencesAreEmpty() throws Exception {
Map<String, String> preferences = singletonMap("theia-user-preferences", "{}");
when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences);

gitUserProfileProvisioner.provision(k8sEnv, runtimeIdentity);

verifyZeroInteractions(runtimeIdentity);
}

@Test
public void testShouldCheckIfPodHasMountAndK8HasConfigMapForGitConfig() throws Exception {
String json = "{\"git.user.name\":\"user\",\"git.user.email\":\"email\"}";
Map<String, String> preferences = singletonMap("theia-user-preferences", json);
when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences);
when(runtimeIdentity.getWorkspaceId()).thenReturn("wksp");

ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build();
when(pod.getMetadata()).thenReturn(podMeta);
when(pod.getSpec()).thenReturn(podSpec);
when(podSpec.getContainers()).thenReturn(singletonList(container));

List<VolumeMount> volumeMounts = new ArrayList<>();

when(container.getVolumeMounts()).thenReturn(volumeMounts);
k8sEnv.addPod(pod);

gitUserProfileProvisioner.provision(k8sEnv, runtimeIdentity);

assertEquals(volumeMounts.size(), 1);

VolumeMount mount = volumeMounts.get(0);

assertEquals(mount.getMountPath(), "/etc/gitconfig");
assertEquals(mount.getName(), "gitconfigvolume");
assertFalse(mount.getReadOnly());
assertEquals(mount.getSubPath(), "gitconfig");

assertEquals(k8sEnv.getConfigMaps().size(), 1);
assertTrue(k8sEnv.getConfigMaps().containsKey("wksp-gitconfig"));

ConfigMap configMap = k8sEnv.getConfigMaps().get("wksp-gitconfig");

assertEquals(configMap.getData().size(), 1);
assertTrue(configMap.getData().containsKey("gitconfig"));

String gitconfig = configMap.getData().get("gitconfig");
String expectedGitconfig = "[user]\n\tname = user\n\temail = email\n";

assertEquals(gitconfig, expectedGitconfig);
}
}

0 comments on commit 6b4bd00

Please sign in to comment.