CHE-11349 Make Kubernetes Infrastructure create workspace service account (PR #11834)

6.19.x
Sergii Leshchenko 2018-11-05 15:57:41 +02:00
parent 8089d3d979
commit 7b6cd44e36
6 changed files with 244 additions and 14 deletions

View File

@ -31,14 +31,17 @@ public class KubernetesNamespaceFactory {
private final String namespaceName;
private final boolean isPredefined;
private final String serviceAccountName;
private final KubernetesClientFactory clientFactory;
@Inject
public KubernetesNamespaceFactory(
@Nullable @Named("che.infra.kubernetes.namespace") String namespaceName,
@Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName,
KubernetesClientFactory clientFactory) {
this.namespaceName = namespaceName;
this.isPredefined = !isNullOrEmpty(namespaceName);
this.serviceAccountName = serviceAccountName;
this.clientFactory = clientFactory;
}
@ -64,6 +67,16 @@ public class KubernetesNamespaceFactory {
final String namespaceName = isPredefined ? this.namespaceName : workspaceId;
KubernetesNamespace namespace = doCreateNamespace(workspaceId, namespaceName);
namespace.prepare();
if (!isPredefined() && !isNullOrEmpty(serviceAccountName)) {
// prepare service account for workspace only if account name is configured
// and project is not predefined
// since predefined project should be prepared during Che deployment
KubernetesWorkspaceServiceAccount workspaceServiceAccount =
doCreateServiceAccount(workspaceId, namespaceName);
workspaceServiceAccount.prepare();
}
return namespace;
}
@ -83,4 +96,11 @@ public class KubernetesNamespaceFactory {
KubernetesNamespace doCreateNamespace(String workspaceId, String name) {
return new KubernetesNamespace(clientFactory, name, workspaceId);
}
@VisibleForTesting
KubernetesWorkspaceServiceAccount doCreateServiceAccount(
String workspaceId, String namespaceName) {
return new KubernetesWorkspaceServiceAccount(
workspaceId, namespaceName, serviceAccountName, clientFactory);
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.namespace;
import io.fabric8.kubernetes.api.model.rbac.KubernetesPolicyRuleBuilder;
import io.fabric8.kubernetes.api.model.rbac.KubernetesRole;
import io.fabric8.kubernetes.api.model.rbac.KubernetesRoleBinding;
import io.fabric8.kubernetes.api.model.rbac.KubernetesRoleBindingBuilder;
import io.fabric8.kubernetes.api.model.rbac.KubernetesRoleBuilder;
import io.fabric8.kubernetes.api.model.rbac.KubernetesSubjectBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
/**
* Holds logic for preparing workspace service account.
*
* <p>It checks that required service account, roles and role bindings exist and creates if needed.
*
* @author Sergii Leshchenko
*/
public class KubernetesWorkspaceServiceAccount {
private final String namespace;
private final String serviceAccountName;
private final KubernetesClientFactory clientFactory;
private final String workspaceId;
public KubernetesWorkspaceServiceAccount(
String workspaceId,
String namespace,
String serviceAccountName,
KubernetesClientFactory clientFactory) {
this.workspaceId = workspaceId;
this.namespace = namespace;
this.serviceAccountName = serviceAccountName;
this.clientFactory = clientFactory;
}
/**
* Make sure that workspace service account exists and has `view` and `exec` role bindings.
*
* <p>Note that `view` role is used from cluster scope and `exec` role is created in the current
* namespace if does not exit.
*
* @throws InfrastructureException when any exception occurred
*/
void prepare() throws InfrastructureException {
KubernetesClient k8sClient = clientFactory.create(workspaceId);
if (k8sClient.serviceAccounts().inNamespace(namespace).withName(serviceAccountName).get()
== null) {
createWorkspaceServiceAccount(k8sClient);
}
String execRoleName = "exec";
if (k8sClient.rbac().kubernetesRoles().inNamespace(namespace).withName(execRoleName).get()
== null) {
createExecRole(k8sClient, execRoleName);
}
k8sClient
.rbac()
.kubernetesRoleBindings()
.inNamespace(namespace)
.createOrReplace(createExecRoleBinding());
k8sClient
.rbac()
.kubernetesRoleBindings()
.inNamespace(namespace)
.createOrReplace(createViewRoleBinding());
}
private void createWorkspaceServiceAccount(KubernetesClient k8sClient) {
k8sClient
.serviceAccounts()
.inNamespace(namespace)
.createOrReplaceWithNew()
.withAutomountServiceAccountToken(true)
.withNewMetadata()
.withName(serviceAccountName)
.endMetadata()
.done();
}
private void createExecRole(KubernetesClient k8sClient, String name) {
KubernetesRole execRole =
new KubernetesRoleBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withRules(
new KubernetesPolicyRuleBuilder()
.withResources("pods/exec")
.withApiGroups("")
.withVerbs("create")
.build())
.build();
k8sClient.rbac().kubernetesRoles().inNamespace(namespace).create(execRole);
}
private KubernetesRoleBinding createViewRoleBinding() {
return new KubernetesRoleBindingBuilder()
.withNewMetadata()
.withName(serviceAccountName + "-view")
.withNamespace(namespace)
.endMetadata()
.withNewRoleRef()
.withKind("ClusterRole")
.withName("view")
.endRoleRef()
.withSubjects(
new KubernetesSubjectBuilder()
.withKind("ServiceAccount")
.withName(serviceAccountName)
.withNamespace(namespace)
.build())
.build();
}
private KubernetesRoleBinding createExecRoleBinding() {
return new KubernetesRoleBindingBuilder()
.withNewMetadata()
.withName(serviceAccountName + "-exec")
.withNamespace(namespace)
.endMetadata()
.withNewRoleRef()
.withKind("Role")
.withName("exec")
.endRoleRef()
.withSubjects(
new KubernetesSubjectBuilder()
.withKind("ServiceAccount")
.withName(serviceAccountName)
.withNamespace(namespace)
.build())
.build();
}
}

View File

@ -40,7 +40,7 @@ public class KubernetesNamespaceFactoryTest {
@Test
public void shouldReturnTrueIfNamespaceIsNotEmptyOnCheckingIfNamespaceIsPredefined() {
// given
namespaceFactory = new KubernetesNamespaceFactory("predefined", clientFactory);
namespaceFactory = new KubernetesNamespaceFactory("predefined", "", clientFactory);
// when
boolean isPredefined = namespaceFactory.isPredefined();
@ -52,7 +52,7 @@ public class KubernetesNamespaceFactoryTest {
@Test
public void shouldReturnTrueIfNamespaceIsEmptyOnCheckingIfNamespaceIsPredefined() {
// given
namespaceFactory = new KubernetesNamespaceFactory("", clientFactory);
namespaceFactory = new KubernetesNamespaceFactory("", "", clientFactory);
// when
boolean isPredefined = namespaceFactory.isPredefined();
@ -64,7 +64,7 @@ public class KubernetesNamespaceFactoryTest {
@Test
public void shouldReturnTrueIfNamespaceIsNullOnCheckingIfNamespaceIsPredefined() {
// given
namespaceFactory = new KubernetesNamespaceFactory(null, clientFactory);
namespaceFactory = new KubernetesNamespaceFactory(null, "", clientFactory);
// when
boolean isPredefined = namespaceFactory.isPredefined();
@ -76,7 +76,7 @@ public class KubernetesNamespaceFactoryTest {
@Test
public void shouldCreateAndPrepareNamespaceWithPredefinedValueIfItIsNotEmpty() throws Exception {
// given
namespaceFactory = spy(new KubernetesNamespaceFactory("predefined", clientFactory));
namespaceFactory = spy(new KubernetesNamespaceFactory("predefined", "", clientFactory));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any());
@ -93,7 +93,7 @@ public class KubernetesNamespaceFactoryTest {
public void shouldCreateAndPrepareNamespaceWithWorkspaceIdAsNameIfConfiguredNameIsNotPredefined()
throws Exception {
// given
namespaceFactory = spy(new KubernetesNamespaceFactory("", clientFactory));
namespaceFactory = spy(new KubernetesNamespaceFactory("", "", clientFactory));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any());
@ -111,7 +111,7 @@ public class KubernetesNamespaceFactoryTest {
shouldCreateNamespaceAndDoNotPrepareNamespaceOnCreatingNamespaceWithWorkspaceIdAndNameSpecified()
throws Exception {
// given
namespaceFactory = spy(new KubernetesNamespaceFactory("", clientFactory));
namespaceFactory = spy(new KubernetesNamespaceFactory("", "", clientFactory));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any());
@ -123,4 +123,63 @@ public class KubernetesNamespaceFactoryTest {
verify(namespaceFactory).doCreateNamespace("workspace123", "name");
verify(toReturnNamespace, never()).prepare();
}
@Test
public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndNamespaceIsNotPredefined()
throws Exception {
// given
namespaceFactory = spy(new KubernetesNamespaceFactory("", "serviceAccount", clientFactory));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any());
KubernetesWorkspaceServiceAccount serviceAccount =
mock(KubernetesWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any());
// when
namespaceFactory.create("workspace123");
// then
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123");
verify(serviceAccount).prepare();
}
@Test
public void shouldNotPrepareWorkspaceServiceAccountIfItIsConfiguredAndProjectIsPredefined()
throws Exception {
// given
namespaceFactory =
spy(new KubernetesNamespaceFactory("namespace", "serviceAccount", clientFactory));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any());
KubernetesWorkspaceServiceAccount serviceAccount =
mock(KubernetesWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any());
// when
namespaceFactory.create("workspace123");
// then
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
}
@Test
public void shouldNotPrepareWorkspaceServiceAccountIfItIsNotConfiguredAndProjectIsNotPredefined()
throws Exception {
// given
namespaceFactory = spy(new KubernetesNamespaceFactory("", "", clientFactory));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any());
KubernetesWorkspaceServiceAccount serviceAccount =
mock(KubernetesWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any());
// when
namespaceFactory.create("workspace123");
// then
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
}
}

View File

@ -39,7 +39,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
@Nullable @Named("che.infra.openshift.project") String projectName,
@Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName,
OpenShiftClientFactory clientFactory) {
super(projectName, clientFactory);
super(projectName, serviceAccountName, clientFactory);
this.projectName = projectName;
this.serviceAccountName = serviceAccountName;
this.clientFactory = clientFactory;
@ -64,9 +64,9 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
// prepare service account for workspace only if account name is configured
// and project is not predefined
// since predefined project should be prepared during Che deployment
WorkspaceServiceAccount workspaceServiceAccount =
OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount =
doCreateServiceAccount(workspaceId, projectName);
workspaceServiceAccount.prepare();
osWorkspaceServiceAccount.prepare();
}
return osProject;
@ -90,7 +90,8 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
}
@VisibleForTesting
WorkspaceServiceAccount doCreateServiceAccount(String workspaceId, String projectName) {
return new WorkspaceServiceAccount(workspaceId, projectName, serviceAccountName, clientFactory);
OpenShiftWorkspaceServiceAccount doCreateServiceAccount(String workspaceId, String projectName) {
return new OpenShiftWorkspaceServiceAccount(
workspaceId, projectName, serviceAccountName, clientFactory);
}
}

View File

@ -27,15 +27,17 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory
* <p>It checks that required service account, roles and role bindings exist and creates if needed.
*
* @author Sergii Leshchenko
* @see
* org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount
*/
class WorkspaceServiceAccount {
class OpenShiftWorkspaceServiceAccount {
private final String projectName;
private final String serviceAccountName;
private final OpenShiftClientFactory clientFactory;
private final String workspaceId;
WorkspaceServiceAccount(
OpenShiftWorkspaceServiceAccount(
String workspaceId,
String projectName,
String serviceAccountName,

View File

@ -77,7 +77,7 @@ public class OpenShiftProjectFactoryTest {
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
doReturn(toReturnProject).when(projectFactory).doCreateProject(any(), any());
WorkspaceServiceAccount serviceAccount = mock(WorkspaceServiceAccount.class);
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(projectFactory).doCreateServiceAccount(any(), any());
// when