CHE-11349 Make Kubernetes Infrastructure create workspace service account (PR #11834)
parent
8089d3d979
commit
7b6cd44e36
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue