Skip to content

Commit

Permalink
merge with main
Browse files Browse the repository at this point in the history
Signed-off-by: Igor Vinokur <[email protected]>
  • Loading branch information
vinokurig committed Aug 4, 2021
2 parents a239963 + 159194c commit 25770d8
Show file tree
Hide file tree
Showing 26 changed files with 705 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ che.database=${che.home}/storage
che.api=http://${CHE_HOST}:${CHE_PORT}/api

# API service internal network URL. Back-end services should initiate REST communications to {prod-short} server with this URL
che.api.internal=http://${CHE_HOST}:${CHE_PORT}/api
che.api.internal=NULL

# {prod-short} WebSocket major endpoint. Provides basic communication endpoint
# for major WebSocket interactions and messaging.
che.websocket.endpoint=ws://${CHE_HOST}:${CHE_PORT}/api/websocket

# {prod-short} WebSocket major internal endpoint. Provides basic communication endpoint
# for major WebSocket interactions and messaging.
che.websocket.internal.endpoint=NULL

# Your projects are synchronized from the {prod-short} server into the machine running each
# workspace. This is the directory in the machine where your projects are placed.
Expand Down Expand Up @@ -309,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`.
Expand All @@ -320,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.
Expand Down Expand Up @@ -742,9 +751,9 @@ che.infra.kubernetes.async.storage.image=quay.io/eclipse/che-workspace-data-sync
# key=value pairs, for example: `disktype=ssd,cpu=xlarge,foo=bar`
che.workspace.pod.node_selector=NULL

# Optionally configures tolerations for workspace Pod. Format is a string representing a JSON Array of taint tolerations,
# or `NULL` to disable it. The objects contained in the array have to follow the
# link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#toleration-v1-core[toleration v1 core specifications].
# Optionally configures tolerations for workspace Pod. Format is a string representing a JSON Array of taint tolerations,
# or `NULL` to disable it. The objects contained in the array have to follow the
# link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#toleration-v1-core[toleration v1 core specifications].
# Example: `[{"effect":"NoExecute","key":"aNodeTaint","operator":"Equal","value":"aValue"}]`
che.workspace.pod.tolerations_json=NULL

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ che.keycloak.realm=che

# Keycloak client identifier in `che.keycloak.realm` to authenticate users in the dashboard, the IDE, and the CLI.
che.keycloak.client_id=che-public

# URL to access OSO OAuth tokens
che.keycloak.oso.endpoint=NULL

Expand Down
16 changes: 4 additions & 12 deletions deploy/kubernetes/helm/che/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ data:
CHE_API: https://{{ template "cheHost" . }}/api
{{- if .Values.global.useInternalClusterSVCNames }}
CHE_API_INTERNAL: http://che-host.{{ .Release.Namespace }}.svc:8080/api
{{- else }}
CHE_API_INTERNAL: https://{{ template "cheHost" . }}/api
{{- end }}
CHE_WEBSOCKET_ENDPOINT: wss://{{ template "cheHost" . }}/api/websocket
CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL: https://{{ template "cheHost" . }}/agent-binaries/linux_amd64/bootstrapper/bootstrapper
{{ else }}
CHE_API: http://{{ template "cheHost" . }}/api
{{- if .Values.global.useInternalClusterSVCNames }}
CHE_API_INTERNAL: http://che-host.{{ .Release.Namespace }}.svc:8080/api
{{- else }}
CHE_API_INTERNAL: http://{{ template "cheHost" . }}/api
{{- end }}
CHE_WEBSOCKET_ENDPOINT: ws://{{ template "cheHost" . }}/api/websocket
CHE_WEBSOCKET_ENDPOINT__MINOR: ws://{{ template "cheHost" . }}/api/websocket-minor
CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL: http://{{ template "cheHost" . }}/agent-binaries/linux_amd64/bootstrapper/bootstrapper
{{- end }}
{{- if .Values.global.useInternalClusterSVCNames }}
CHE_WEBSOCKET_INTERNAL_ENDPOINT: ws://che-host.{{ .Release.Namespace }}.svc:8080/api/websocket
CHE_WEBSOCKET_INTERNAL_ENDPOINT__MINOR: ws://che-host.{{ .Release.Namespace }}.svc:8080/api/websocket-minor
{{- end }}
CHE_DEBUG_SERVER: "true"
CHE_INFRASTRUCTURE_ACTIVE: "kubernetes"
Expand All @@ -54,8 +54,6 @@ data:
CHE_KEYCLOAK_AUTH__SERVER__URL: {{ template "keycloakAuthUrl" . }}
{{- if .Values.global.useInternalClusterSVCNames }}
CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL: http://keycloak.{{ .Release.Namespace }}.svc:5050/auth
{{- else }}
CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL: {{ template "keycloakAuthUrl" . }}
{{- end }}
CHE_KEYCLOAK_REALM: {{ .Values.cheKeycloakRealm }}
{{- end }}
Expand Down Expand Up @@ -121,24 +119,18 @@ data:
{{- end }}
{{- if .Values.che.workspace.devfileRegistryUrl }}
CHE_WORKSPACE_DEVFILE__REGISTRY__URL: {{ .Values.che.workspace.devfileRegistryUrl | quote }}
CHE_WORKSPACE_DEVFILE__REGISTRY__INTERNAL__URL: {{ .Values.che.workspace.devfileRegistryUrl | quote }}
{{- else if .Values.cheDevfileRegistry.deploy }}
CHE_WORKSPACE_DEVFILE__REGISTRY__URL: {{ template "devfileRegistryUrl" . }}
{{- if .Values.global.useInternalClusterSVCNames }}
CHE_WORKSPACE_DEVFILE__REGISTRY__INTERNAL__URL: http://devfile-registry.{{ .Release.Namespace }}.svc:8080
{{- else }}
CHE_WORKSPACE_DEVFILE__REGISTRY__INTERNAL__URL: {{ template "devfileRegistryUrl" . }}
{{- end }}
{{- end }}
{{- if .Values.che.workspace.pluginRegistryUrl }}
CHE_WORKSPACE_PLUGIN__REGISTRY__URL: {{ .Values.che.workspace.pluginRegistryUrl | quote }}
CHE_WORKSPACE_PLUGIN__REGISTRY__INTERNAL__URL: {{ .Values.che.workspace.pluginRegistryUrl | quote }}
{{- else if .Values.chePluginRegistry.deploy }}
CHE_WORKSPACE_PLUGIN__REGISTRY__URL: {{ template "pluginRegistryUrl" . }}
{{- if .Values.global.useInternalClusterSVCNames }}
CHE_WORKSPACE_PLUGIN__REGISTRY__INTERNAL__URL: http://plugin-registry.{{ .Release.Namespace }}.svc:8080/v3
{{- else }}
CHE_WORKSPACE_PLUGIN__REGISTRY__INTERNAL__URL: {{ template "pluginRegistryUrl" . }}
{{- end }}
{{- end }}
{{- if .Values.che.workspace.pluginBroker }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto;
Expand Down Expand Up @@ -63,6 +66,27 @@ public List<KubernetesNamespaceMetaDto> getNamespaces() throws InfrastructureExc
return namespaceFactory.list().stream().map(this::asDto).collect(Collectors.toList());
}

@POST
@Path("provision")
@Produces(APPLICATION_JSON)
@ApiOperation(
value = "Provision k8s namespace where user is able to create workspaces",
notes =
"This operation can be performed only by an authorized user."
+ " This is a beta feature that may be significantly changed.",
response = KubernetesNamespaceMetaDto.class)
@ApiResponses({
@ApiResponse(code = 200, message = "The namespace successfully provisioned"),
@ApiResponse(
code = 500,
message = "Internal server error occurred during namespace provisioning")
})
public KubernetesNamespaceMetaDto provision() throws InfrastructureException {
return asDto(
namespaceFactory.provision(
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject())));
}

private KubernetesNamespaceMetaDto asDto(KubernetesNamespaceMeta kubernetesNamespaceMeta) {
return DtoFactory.newDto(KubernetesNamespaceMetaDto.class)
.withName(kubernetesNamespaceMeta.getName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,19 @@ public 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);

Expand All @@ -161,6 +167,7 @@ void prepare(boolean canCreate, Map<String, String> labels) throws Infrastructur
client.secrets().inNamespace(name).create(secret);
}
label(namespace, labels);
annotate(namespace, annotations);
}

/**
Expand Down Expand Up @@ -215,6 +222,58 @@ protected void label(Namespace namespace, Map<String, String> ensureLabels)
}
}

/**
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.user.server.PreferenceManager;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
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;
Expand Down Expand Up @@ -93,6 +94,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;

Expand All @@ -112,6 +114,7 @@ public 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,
Expand All @@ -129,6 +132,7 @@ public KubernetesNamespaceFactory(
this.preferenceManager = preferenceManager;
this.sharedPool = sharedPool;
this.labelNamespaces = labelNamespaces;
this.annotateNamespaces = annotateNamespaces;

//noinspection UnstableApiUsage
Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("=");
Expand Down Expand Up @@ -327,7 +331,15 @@ protected boolean isWorkspaceNamespaceManaged(String namespaceName, Workspace wo
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 =
Expand All @@ -338,6 +350,21 @@ public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws Infrastr
return namespace;
}

public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
KubernetesNamespace namespace =
getOrCreate(
new RuntimeIdentityImpl(
null,
null,
namespaceResolutionContext.getUserId(),
evaluateNamespaceName(namespaceResolutionContext)));

return fetchNamespace(namespace.getName())
.orElseThrow(
() -> new InfrastructureException("Not able to find namespace " + namespace.getName()));
}

public KubernetesNamespace get(RuntimeIdentity identity) throws InfrastructureException {
String workspaceId = identity.getWorkspaceId();
String namespaceName = identity.getInfrastructureNamespace();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* Copyright (c) 2012-2021 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/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;

import static com.google.common.base.Strings.isNullOrEmpty;

import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiInternalEnvVarProvider;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.lang.Pair;

/**
Expand All @@ -29,12 +32,15 @@ public class KubernetesCheApiInternalEnvVarProvider implements CheApiInternalEnv

@Inject
public KubernetesCheApiInternalEnvVarProvider(
@Named("che.api.internal") String cheServerEndpoint) {
@Nullable @Named("che.api.internal") String cheServerEndpoint) {
this.cheServerEndpoint = cheServerEndpoint;
}

@Override
public Pair<String, String> get(RuntimeIdentity runtimeIdentity) throws InfrastructureException {
if (isNullOrEmpty(this.cheServerEndpoint)) {
return null;
}
return Pair.of(CHE_API_INTERNAL_VARIABLE, cheServerEndpoint);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,30 @@ public abstract class BrokerEnvironmentFactory<E extends KubernetesEnvironment>

public BrokerEnvironmentFactory(
String cheWebsocketEndpoint,
String cheWebsocketInternalEndpoint,
String brokerPullPolicy,
AgentAuthEnableEnvVarProvider authEnableEnvVarProvider,
MachineTokenEnvVarProvider machineTokenEnvVarProvider,
String artifactsBrokerImage,
String metadataBrokerImage,
String pluginRegistryUrl,
String pluginRegistryExternalUrl,
String pluginRegistryInternalUrl,
TrustedCAProvisioner trustedCAProvisioner,
String certificateMountPath,
CertificateProvisioner certProvisioner) {
this.cheWebsocketEndpoint = cheWebsocketEndpoint;
this.cheWebsocketEndpoint =
isNullOrEmpty(cheWebsocketInternalEndpoint)
? cheWebsocketEndpoint
: cheWebsocketInternalEndpoint;
this.brokerPullPolicy = brokerPullPolicy;
this.authEnableEnvVarProvider = authEnableEnvVarProvider;
this.machineTokenEnvVarProvider = machineTokenEnvVarProvider;
this.artifactsBrokerImage = artifactsBrokerImage;
this.metadataBrokerImage = metadataBrokerImage;
this.pluginRegistryUrl =
isNullOrEmpty(pluginRegistryInternalUrl) ? pluginRegistryUrl : pluginRegistryInternalUrl;
isNullOrEmpty(pluginRegistryInternalUrl)
? pluginRegistryExternalUrl
: pluginRegistryInternalUrl;
this.trustedCAProvisioner = trustedCAProvisioner;
this.certificateMountPath = certificateMountPath;
this.certProvisioner = certProvisioner;
Expand Down
Loading

0 comments on commit 25770d8

Please sign in to comment.