Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provision git configuration into each container #14402

Merged
merged 1 commit into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of static imports of methods such as these, since names like of are not descriptive at all. Up to you though, I know we do it all over the codebase currently.


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);
}
}