From 36d722335a56d6ac63ec360fce0eceb0442c4c03 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 11:11:05 -0500 Subject: [PATCH 01/16] Move flatten library separate package and add plugin resolving functions Add a library function ResolveDevWorkspace, which is intended to manage flattening of DevWorkspaces with parents and plugins. An initial, basic implementation supports only flattening plugins defined via kubernetes reference. Contains a multitude of TODOs for next steps. Signed-off-by: Angel Misevski --- pkg/library/container/container.go | 4 +- pkg/library/{flatten.go => flatten/common.go} | 2 +- pkg/library/flatten/flatten.go | 108 ++++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) rename pkg/library/{flatten.go => flatten/common.go} (97%) create mode 100644 pkg/library/flatten/flatten.go diff --git a/pkg/library/container/container.go b/pkg/library/container/container.go index 1d5b0ea7f..8b7c9809e 100644 --- a/pkg/library/container/container.go +++ b/pkg/library/container/container.go @@ -26,7 +26,7 @@ import ( devworkspace "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" - "github.com/devfile/devworkspace-operator/pkg/library" + "github.com/devfile/devworkspace-operator/pkg/library/flatten" "github.com/devfile/devworkspace-operator/pkg/library/lifecycle" ) @@ -41,7 +41,7 @@ import ( // // Note: Requires DevWorkspace to be flattened (i.e. the DevWorkspace contains no Parent or Components of type Plugin) func GetKubeContainersFromDevfile(workspace devworkspace.DevWorkspaceTemplateSpec) (*v1alpha1.PodAdditions, error) { - if !library.DevWorkspaceIsFlattened(workspace) { + if !flatten.DevWorkspaceIsFlattened(workspace) { return nil, fmt.Errorf("devfile is not flattened") } podAdditions := &v1alpha1.PodAdditions{} diff --git a/pkg/library/flatten.go b/pkg/library/flatten/common.go similarity index 97% rename from pkg/library/flatten.go rename to pkg/library/flatten/common.go index fd6d6ae02..6ac5e8cd7 100644 --- a/pkg/library/flatten.go +++ b/pkg/library/flatten/common.go @@ -10,7 +10,7 @@ // Red Hat, Inc. - initial API and implementation // -package library +package flatten import devworkspace "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go new file mode 100644 index 000000000..54fb7d934 --- /dev/null +++ b/pkg/library/flatten/flatten.go @@ -0,0 +1,108 @@ +// +// Copyright (c) 2019-2020 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 flatten + +import ( + "context" + "fmt" + + devworkspace "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/api/pkg/utils/overriding" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ResolverTools struct { + Context context.Context + K8sClient client.Client +} + +// ResolveDevWorkspace takes a devworkspace and returns a "resolved" version of it -- i.e. one where all plugins and parents +// are inlined as components. +// TODO: +// - Implement flattening for DevWorkspace parents +// - Implement plugin references by ID and URI +// - Implement plugin overrides +// - Implement plugin + editor compatibility checking +// - Implement cycle checking for references +func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { + if DevWorkspaceIsFlattened(workspace) { + return workspace.DeepCopy(), nil + } + if workspace.Parent != nil { + // TODO: Add support for flattening DevWorkspace parents + return nil, fmt.Errorf("DevWorkspace parent is unsupported") + } + resolvedContent := &devworkspace.DevWorkspaceTemplateSpecContent{} + resolvedContent.Projects = workspace.Projects + resolvedContent.StarterProjects = workspace.StarterProjects + resolvedContent.Commands = workspace.Commands + resolvedContent.Events = workspace.Events + + var pluginSpecContents []*devworkspace.DevWorkspaceTemplateSpecContent + for _, component := range workspace.Components { + if component.Plugin == nil { + // No action necessary + resolvedContent.Components = append(resolvedContent.Components, component) + } else { + pluginComponent, err := resolvePluginComponent(component.Name, component.Plugin, tooling) + if err != nil { + return nil, err + } + resolvedPlugin, err := ResolveDevWorkspace(*pluginComponent, tooling) + if err != nil { + return nil, err + } + pluginSpecContents = append(pluginSpecContents, &resolvedPlugin.DevWorkspaceTemplateSpecContent) + } + } + resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, nil, pluginSpecContents...) + if err != nil { + return nil, fmt.Errorf("failed to merge DevWorkspace parents/plugins: %w", err) + } + return &devworkspace.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: *resolvedContent, + }, nil +} + +func resolvePluginComponent(name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { + if plugin.Components != nil || plugin.Commands != nil { + // TODO: Add support for overriding plugin components and commands + return nil, fmt.Errorf("plugin overrides is unsupported") + } + switch { + // TODO: Add support for plugin ID and URI + case plugin.Kubernetes != nil: + return resolvePluginComponentByKubernetesReference(name, plugin, tooling) + case plugin.Uri != "": + return nil, fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) + case plugin.Id != "": + return nil, fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) + default: + return nil, fmt.Errorf("plugin %s does not define any resources", name) + } +} + +func resolvePluginComponentByKubernetesReference(name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { + var dwTemplate devworkspace.DevWorkspaceTemplate + namespacedName := types.NamespacedName{ + Name: plugin.Kubernetes.Name, + Namespace: plugin.Kubernetes.Namespace, + } + err := tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) + if err != nil { + return nil, fmt.Errorf("failed to retrieve referenced kubernetes name and namespace for plugin %s: %w", name, err) + } + + return &dwTemplate.Spec, nil +} From e4e2bb866d20dfa2bf8a0b0bf69607f6691599dc Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 12:07:26 -0500 Subject: [PATCH 02/16] Use flatten library in main reconcile loop for DevWorkspace Temporarily use the flatten library in the main reconcile loop for DevWorkspaces; this should eventually be moved to a subcontroller. Signed-off-by: Angel Misevski --- .../workspace/devworkspace_controller.go | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 4fecdb124..56da4566b 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -19,6 +19,8 @@ import ( "strings" "time" + "github.com/devfile/devworkspace-operator/pkg/library/flatten" + containerlib "github.com/devfile/devworkspace-operator/pkg/library/container" shimlib "github.com/devfile/devworkspace-operator/pkg/library/shim" storagelib "github.com/devfile/devworkspace-operator/pkg/library/storage" @@ -169,11 +171,23 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct "to reconfigure Operator." return reconcile.Result{}, nil } - timing.SetTime(workspace, timing.ComponentsCreated) - // TODO#185 : Move away from using devfile 1.0 constructs; only work on flattened devfiles until - // TODO#185 : plugins is figured out. + // TODO#185 : Temporarily do devfile flattening in main reconcile loop; this should be moved to a subcontroller. // TODO#185 : Implement defaulting container component for Web Terminals for compatibility + flattenHelpers := flatten.ResolverTools{ + Context: ctx, + K8sClient: r.Client, + } + flattenedWorkspace, err := flatten.ResolveDevWorkspace(workspace.Spec.Template, flattenHelpers) + if err != nil { + reqLogger.Info("DevWorkspace start failed") + reconcileStatus.Phase = devworkspace.WorkspaceStatusFailed + // TODO: Handle error more elegantly + reconcileStatus.Conditions[devworkspace.WorkspaceFailedStart] = fmt.Sprintf("Error processing devfile: %s", err) + return reconcile.Result{}, nil + } + workspace.Spec.Template = *flattenedWorkspace + devfilePodAdditions, err := containerlib.GetKubeContainersFromDevfile(workspace.Spec.Template) if err != nil { reqLogger.Info("DevWorkspace start failed") From 303c1bcf7169e1c5c6a8172c101279cb116de3c1 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 18:52:09 -0500 Subject: [PATCH 03/16] Ensure we don't update devworkspace with flattened content in reconcile Since the reconcile loop has to work on the flattened devworkspace contents while updating annotations on the original object, some care is required. To this end: * Make a copy of the original devworkspace from the cluster, which is the only object to be updated * Rework startup timing functionality to not involve directly modifying the devworkspace resource; instead use a map that we merge into the (cluster) workspace annotations only when needed. Signed-off-by: Angel Misevski --- .../workspace/devworkspace_controller.go | 49 +++++++++++-------- pkg/timing/timing.go | 12 ++--- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 56da4566b..ad2deacae 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -125,7 +125,7 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct // Handle stopped workspaces if !workspace.Spec.Started { timing.ClearAnnotations(workspace) - r.syncTimingToCluster(ctx, workspace, reqLogger) + r.syncTimingToCluster(ctx, workspace, map[string]string{}, reqLogger) return r.stopWorkspace(workspace, reqLogger) } @@ -141,27 +141,28 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct Conditions: map[devworkspace.WorkspaceConditionType]string{}, Phase: devworkspace.WorkspaceStatusStarting, } - timing.SetTime(workspace, timing.WorkspaceStarted) + clusterWorkspace := workspace.DeepCopy() + timingInfo := map[string]string{} + timing.SetTime(timingInfo, timing.WorkspaceStarted) defer func() (reconcile.Result, error) { - r.syncTimingToCluster(ctx, workspace, reqLogger) - return r.updateWorkspaceStatus(workspace, reqLogger, &reconcileStatus, reconcileResult, err) + r.syncTimingToCluster(ctx, clusterWorkspace, timingInfo, reqLogger) + return r.updateWorkspaceStatus(clusterWorkspace, reqLogger, &reconcileStatus, reconcileResult, err) }() - msg, err := r.validateCreatorTimestamp(workspace) + msg, err := r.validateCreatorTimestamp(clusterWorkspace) if err != nil { reconcileStatus.Phase = devworkspace.WorkspaceStatusFailed reconcileStatus.Conditions[devworkspace.WorkspaceFailedStart] = msg return reconcile.Result{}, err } - _, ok := workspace.Annotations[config.WorkspaceStopReasonAnnotation] - if ok { - delete(workspace.Annotations, config.WorkspaceStopReasonAnnotation) - err = r.Update(context.TODO(), workspace) + if _, ok := clusterWorkspace.Annotations[config.WorkspaceStopReasonAnnotation]; ok { + delete(clusterWorkspace.Annotations, config.WorkspaceStopReasonAnnotation) + err = r.Update(context.TODO(), clusterWorkspace) return reconcile.Result{Requeue: true}, err } - restrictedAccess := workspace.Annotations[config.WorkspaceRestrictedAccessAnnotation] + restrictedAccess := clusterWorkspace.Annotations[config.WorkspaceRestrictedAccessAnnotation] if restrictedAccess == "true" && config.ControllerCfg.GetWebhooksEnabled() != "true" { reqLogger.Info("Workspace is configured to have restricted access but webhooks are not enabled.") reconcileStatus.Phase = devworkspace.WorkspaceStatusFailed @@ -171,7 +172,8 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct "to reconfigure Operator." return reconcile.Result{}, nil } - timing.SetTime(workspace, timing.ComponentsCreated) + + timing.SetTime(timingInfo, timing.ComponentsCreated) // TODO#185 : Temporarily do devfile flattening in main reconcile loop; this should be moved to a subcontroller. // TODO#185 : Implement defaulting container component for Web Terminals for compatibility flattenHelpers := flatten.ResolverTools{ @@ -211,7 +213,7 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct return reconcile.Result{}, nil } reconcileStatus.Conditions[devworkspace.WorkspaceReady] = "" - timing.SetTime(workspace, timing.ComponentsReady) + timing.SetTime(timingInfo, timing.ComponentsReady) // Only add che rest apis if Theia editor is present in the devfile if restapis.IsCheRestApisRequired(workspace.Spec.Template.Components) { @@ -239,7 +241,7 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct } // Step two: Create routing, and wait for routing to be ready - timing.SetTime(workspace, timing.RoutingCreated) + timing.SetTime(timingInfo, timing.RoutingCreated) routingStatus := provision.SyncRoutingToCluster(workspace, componentDescriptions, clusterAPI) if !routingStatus.Continue { if routingStatus.FailStartup { @@ -253,9 +255,9 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct return reconcile.Result{Requeue: routingStatus.Requeue}, routingStatus.Err } reconcileStatus.Conditions[devworkspace.WorkspaceRoutingReady] = "" - timing.SetTime(workspace, timing.RoutingReady) + timing.SetTime(timingInfo, timing.RoutingReady) - statusOk, err := syncWorkspaceIdeURL(workspace, routingStatus.ExposedEndpoints, clusterAPI) + statusOk, err := syncWorkspaceIdeURL(clusterWorkspace, routingStatus.ExposedEndpoints, clusterAPI) if err != nil { return reconcile.Result{}, err } @@ -299,7 +301,7 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct reconcileStatus.Conditions[devworkspace.WorkspaceServiceAccountReady] = "" // Step six: Create deployment and wait for it to be ready - timing.SetTime(workspace, timing.DeploymentCreated) + timing.SetTime(timingInfo, timing.DeploymentCreated) deploymentStatus := provision.SyncDeploymentToCluster(workspace, podAdditions, serviceAcctName, clusterAPI) if !deploymentStatus.Continue { if deploymentStatus.FailStartup { @@ -312,17 +314,17 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct return reconcile.Result{Requeue: deploymentStatus.Requeue}, deploymentStatus.Err } reconcileStatus.Conditions[devworkspace.WorkspaceReady] = "" - timing.SetTime(workspace, timing.DeploymentReady) + timing.SetTime(timingInfo, timing.DeploymentReady) - serverReady, err := checkServerStatus(workspace) + serverReady, err := checkServerStatus(clusterWorkspace) if err != nil { return reconcile.Result{}, err } if !serverReady { return reconcile.Result{RequeueAfter: 1 * time.Second}, nil } - timing.SetTime(workspace, timing.WorkspaceReady) - timing.SummarizeStartup(workspace) + timing.SetTime(timingInfo, timing.WorkspaceReady) + timing.SummarizeStartup(clusterWorkspace) reconcileStatus.Phase = devworkspace.WorkspaceStatusRunning return reconcile.Result{}, nil } @@ -365,8 +367,13 @@ func (r *DevWorkspaceReconciler) stopWorkspace(workspace *devworkspace.DevWorksp } func (r *DevWorkspaceReconciler) syncTimingToCluster( - ctx context.Context, workspace *devworkspace.DevWorkspace, reqLogger logr.Logger) { + ctx context.Context, workspace *devworkspace.DevWorkspace, timingInfo map[string]string, reqLogger logr.Logger) { if timing.IsEnabled() { + for timingEvent, timestamp := range timingInfo { + if _, set := workspace.Annotations[timingEvent]; !set { + workspace.Annotations[timingEvent] = timestamp + } + } if err := r.Update(ctx, workspace); err != nil { if k8sErrors.IsConflict(err) { reqLogger.Info("Got conflict when trying to apply timing annotations to workspace") diff --git a/pkg/timing/timing.go b/pkg/timing/timing.go index cc55e3cdf..f933a1a06 100644 --- a/pkg/timing/timing.go +++ b/pkg/timing/timing.go @@ -29,17 +29,17 @@ func IsEnabled() bool { // SetTime applies a given event annotation to the devworkspace with the current // timestamp. No-op if timing is disabled or the annotation is already set, meaning // this function can be called without additional checks. -func SetTime(workspace *devworkspace.DevWorkspace, event string) { +func SetTime(timingInfo map[string]string, event string) { if !IsEnabled() { return } - if _, set := workspace.Annotations[event]; set { - return + if timingInfo == nil { + timingInfo = map[string]string{} } - if workspace.Annotations == nil { - workspace.Annotations = map[string]string{} + if _, set := timingInfo[event]; set { + return } - workspace.Annotations[event] = strconv.FormatInt(time.Now().UnixNano()/1e6, 10) + timingInfo[event] = strconv.FormatInt(time.Now().UnixNano()/1e6, 10) } // SummarizeStartup applies aggregate annotations based off event annotations set by From eb40b060b333e6fcd56e8d1bf065c9f2f16702bc Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 12:20:40 -0500 Subject: [PATCH 04/16] Add plugin DevWorkspaceTemplates and DevWorkspaces that reference them * Add samples/plugins directory containing DevWorkspaceTemplates defining plugins used in sample devfiles * Add Makefile rule install_plugin_templates which deploys above templates to devworkspace-plugins namespace * Add samples/with-k8s-ref directory containing DevWorkspace samples that reference plugins via DevWorkspaceTemplate Signed-off-by: Angel Misevski --- Makefile | 5 + go.sum | 3 + samples/plugins/machine-exec.yaml | 28 +++++ samples/plugins/theia-next.yaml | 115 ++++++++++++++++++ samples/plugins/vscode-typescript.yaml | 29 +++++ samples/plugins/web-terminal-dev.yaml | 26 ++++ samples/plugins/web-terminal.yaml | 27 ++++ samples/with-k8s-ref/theia-next.yaml | 29 +++++ samples/with-k8s-ref/theia-nodejs.yaml | 81 ++++++++++++ .../web-terminal-custom-tooling.yaml | 27 ++++ samples/with-k8s-ref/web-terminal-dev.yaml | 19 +++ samples/with-k8s-ref/web-terminal.yaml | 19 +++ 12 files changed, 408 insertions(+) create mode 100644 samples/plugins/machine-exec.yaml create mode 100644 samples/plugins/theia-next.yaml create mode 100644 samples/plugins/vscode-typescript.yaml create mode 100644 samples/plugins/web-terminal-dev.yaml create mode 100644 samples/plugins/web-terminal.yaml create mode 100644 samples/with-k8s-ref/theia-next.yaml create mode 100644 samples/with-k8s-ref/theia-nodejs.yaml create mode 100644 samples/with-k8s-ref/web-terminal-custom-tooling.yaml create mode 100644 samples/with-k8s-ref/web-terminal-dev.yaml create mode 100644 samples/with-k8s-ref/web-terminal.yaml diff --git a/Makefile b/Makefile index a2a9d4217..a3e852e40 100644 --- a/Makefile +++ b/Makefile @@ -199,6 +199,11 @@ endif mv config/base/config.properties.bak config/base/config.properties mv config/base/manager_image_patch.yaml.bak config/base/manager_image_patch.yaml +### install_plugin_templates: Deploy sample plugin templates to namespace devworkspace-plugins: +install_plugin_templates: _print_vars + $(K8S_CLI) create namespace devworkspace-plugins || true + $(K8S_CLI) apply -f samples/plugins -n devworkspace-plugins + ### restart: Restart devworkspace-controller deployment restart: $(K8S_CLI) rollout restart -n $(NAMESPACE) deployment/devworkspace-controller-manager diff --git a/go.sum b/go.sum index d4a44f465..63a726711 100644 --- a/go.sum +++ b/go.sum @@ -191,7 +191,9 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -243,6 +245,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= diff --git a/samples/plugins/machine-exec.yaml b/samples/plugins/machine-exec.yaml new file mode 100644 index 000000000..53ca3b432 --- /dev/null +++ b/samples/plugins/machine-exec.yaml @@ -0,0 +1,28 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: machine-exec +spec: + components: + - name: che-machine-exec + attributes: + "app.kubernetes.io/name": che-terminal.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": terminal + container: + image: "quay.io/eclipse/che-machine-exec:7.20.0" + command: ['/go/bin/che-machine-exec'] + args: + - '--url' + - '0.0.0.0:4444' + - '--pod-selector' + - controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID) + endpoints: + - name: "che-mach-exec" + exposure: public + targetPort: 4444 + protocol: ws + secure: true + attributes: + type: terminal + cookiesAuthEnabled: "true" diff --git a/samples/plugins/theia-next.yaml b/samples/plugins/theia-next.yaml new file mode 100644 index 000000000..f9b011fd6 --- /dev/null +++ b/samples/plugins/theia-next.yaml @@ -0,0 +1,115 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: theia-next +spec: + components: + - name: plugins + volume: {} + - name: remote-endpoint + volume: {} # TODO: Fix this once ephemeral volumes are supported + - name: vsx-installer # Mainly reads the container objects and searches for those + # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls + # Those found in the dedicated containers components are with a sidecar, + # Those found in the che-theia container are without a sidecar. + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: + args: + - /bin/sh + - '-c' + - > + workspace=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}) && for container in $(echo $workspace | sed -e 's;[[,]\({"attributes":{"app.kubernetes.io\);\n\1;g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do dest=$(echo "$container" | sed 's;.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*;\1;' - ) ; urls=$(echo "$container" | sed 's;.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*;\1;' - ) ; mkdir -p $dest; for url in $(echo $urls | sed 's/[",]/ /g' - ); do echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url); done; done + image: 'quay.io/samsahai/curl:latest' + volumeMounts: + - path: "/plugins" + name: plugins + - name: remote-runtime-injector + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: #### corresponds to `initContainer` definition in old meta.yaml. + image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + - name: theia-ide + attributes: + "app.kubernetes.io/name": che-theia.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": editor + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the vscode-pull-request-github vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://github.com/microsoft/vscode-pull-request-github/releases/download/v0.8.0/vscode-pull-request-github-0.8.0.vsix + container: + image: "quay.io/eclipse/che-theia:next" + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: "3130" + - name: THEIA_HOST + value: 0.0.0.0 + volumeMounts: + - path: "/plugins" + name: plugins + mountSources: true + memoryLimit: "512M" + endpoints: + - name: "theia" + exposure: public + targetPort: 3100 + secure: true + protocol: http + attributes: + type: ide + cookiesAuthEnabled: "true" + - name: "webviews" + exposure: public + targetPort: 3100 + protocol: http + secure: true + attributes: + type: webview + cookiesAuthEnabled: "true" + unique: "true" + - name: "theia-dev" + exposure: public + targetPort: 3130 + protocol: http + attributes: + type: ide-dev + - name: "theia-redir-1" + exposure: public + targetPort: 13131 + protocol: http + - name: "theia-redir-2" + exposure: public + targetPort: 13132 + protocol: http + - name: "theia-redir-3" + exposure: public + targetPort: 13133 + protocol: http + commands: + # Commands coming from plugin editor + - id: inject-theia-in-remote-sidecar + apply: + component: remote-runtime-injector + - id: copy-vsx + apply: + component: vsx-installer + events: + preStart: + - inject-theia-in-remote-sidecar + - copy-vsx diff --git a/samples/plugins/vscode-typescript.yaml b/samples/plugins/vscode-typescript.yaml new file mode 100644 index 000000000..6f9571747 --- /dev/null +++ b/samples/plugins/vscode-typescript.yaml @@ -0,0 +1,29 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: vscode-typescript +spec: + components: + - name: sidecar-typescript + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": vscode-plugin + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the typescript vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://download.jboss.org/jbosstools/vscode/3rdparty/ms-code.typescript/che-typescript-language-1.35.1.vsix + + container: + image: "quay.io/eclipse/che-sidecar-node:10-0cb5d78" + memoryLimit: '512Mi' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/vscode-typescript + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + - name: plugins + path: /plugins diff --git a/samples/plugins/web-terminal-dev.yaml b/samples/plugins/web-terminal-dev.yaml new file mode 100644 index 000000000..628d316d6 --- /dev/null +++ b/samples/plugins/web-terminal-dev.yaml @@ -0,0 +1,26 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: web-terminal-dev +spec: + components: + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID)", + "--use-bearer-token"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + cookiesAuthEnabled: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" diff --git a/samples/plugins/web-terminal.yaml b/samples/plugins/web-terminal.yaml new file mode 100644 index 000000000..bc9d0500d --- /dev/null +++ b/samples/plugins/web-terminal.yaml @@ -0,0 +1,27 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: web-terminal +spec: + components: + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + cookiesAuthEnabled: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" diff --git a/samples/with-k8s-ref/theia-next.yaml b/samples/with-k8s-ref/theia-next.yaml new file mode 100644 index 000000000..d25ee653b --- /dev/null +++ b/samples/with-k8s-ref/theia-next.yaml @@ -0,0 +1,29 @@ +kind: DevWorkspace +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: theia +spec: + started: true + template: + projects: + - name: project + git: + remotes: + origin: "https://github.com/che-samples/web-nodejs-sample.git" + components: + - name: theia + plugin: + kubernetes: + name: theia-next + namespace: devworkspace-plugins + - name: terminal + plugin: + kubernetes: + name: machine-exec + namespace: devworkspace-plugins + commands: + - id: say-hello + exec: + component: theia + commandLine: echo "Hello from $(pwd)" + workingDir: ${PROJECTS_ROOT}/project/app diff --git a/samples/with-k8s-ref/theia-nodejs.yaml b/samples/with-k8s-ref/theia-nodejs.yaml new file mode 100644 index 000000000..aec254ab4 --- /dev/null +++ b/samples/with-k8s-ref/theia-nodejs.yaml @@ -0,0 +1,81 @@ +kind: DevWorkspace +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: theia-nodejs +spec: + started: true + template: + projects: + - name: project + git: + remotes: + origin: "https://github.com/che-samples/web-nodejs-sample.git" + components: + - name: che-theia + plugin: + kubernetes: + name: theia-next + namespace: devworkspace-plugins + - name: machine-exec + plugin: + kubernetes: + name: machine-exec + namespace: devworkspace-plugins + - name: typescript + plugin: + kubernetes: + name: vscode-typescript + namespace: devworkspace-plugins + components: + - name: sidecar-typescript + container: + memoryLimit: 512Mi + - name: nodejs + container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + memoryLimit: 512Mi + endpoints: + - name: nodejs + protocol: http + targetPort: 3000 + mountSources: true + commands: + - id: download-dependencies + exec: + component: nodejs + commandLine: npm install + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app + exec: + component: nodejs + commandLine: nodemon app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app-with-debugging-enabled + exec: + component: nodejs + commandLine: nodemon --inspect app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: stop-the-app + exec: + component: nodejs + commandLine: >- + node_server_pids=$(pgrep -fx '.*nodemon (--inspect )?app.js' | tr "\\n" " ") && + echo "Stopping node server with PIDs: ${node_server_pids}" && + kill -15 ${node_server_pids} &>/dev/null && echo 'Done.' + - id: attach-remote-debugger + vscodeLaunch: + inlined: | + { + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Remote", + "address": "localhost", + "port": 9229, + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}" + } + ] + } diff --git a/samples/with-k8s-ref/web-terminal-custom-tooling.yaml b/samples/with-k8s-ref/web-terminal-custom-tooling.yaml new file mode 100644 index 000000000..ea5b473c0 --- /dev/null +++ b/samples/with-k8s-ref/web-terminal-custom-tooling.yaml @@ -0,0 +1,27 @@ +kind: DevWorkspace +apiVersion: workspace.devfile.io/v1alpha1 +metadata: + name: web-terminal + annotations: + controller.devfile.io/restricted-access: "true" + labels: + # it's a label OpenShift console uses a flag to mark terminal's workspaces + console.openshift.io/terminal: "true" +spec: + started: true + routingClass: 'web-terminal' + template: + components: + - plugin: + name: web-terminal + kubernetes: + name: web-terminal + namespace: devworkspace-plugins + - container: + memoryLimit: "256Mi" + name: tooling + image: quay.io/wto/web-terminal-tooling:latest + args: ["tail", "-f", "/dev/null"] + env: + - name: PS1 + value: \[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\] diff --git a/samples/with-k8s-ref/web-terminal-dev.yaml b/samples/with-k8s-ref/web-terminal-dev.yaml new file mode 100644 index 000000000..2164e4d55 --- /dev/null +++ b/samples/with-k8s-ref/web-terminal-dev.yaml @@ -0,0 +1,19 @@ +kind: DevWorkspace +apiVersion: workspace.devfile.io/v1alpha1 +metadata: + name: web-terminal-dev + annotations: + controller.devfile.io/restricted-access: "true" + labels: + # it's a label OpenShift console uses a flag to mark terminal's workspaces + console.openshift.io/terminal: "true" +spec: + started: true + routingClass: 'basic' + template: + components: + - plugin: + name: web-terminal + kubernetes: + name: web-terminal-dev + namespace: devworkspace-plugins diff --git a/samples/with-k8s-ref/web-terminal.yaml b/samples/with-k8s-ref/web-terminal.yaml new file mode 100644 index 000000000..a050dae6a --- /dev/null +++ b/samples/with-k8s-ref/web-terminal.yaml @@ -0,0 +1,19 @@ +kind: DevWorkspace +apiVersion: workspace.devfile.io/v1alpha1 +metadata: + name: web-terminal + annotations: + controller.devfile.io/restricted-access: "true" + labels: + # it's a label OpenShift console uses a flag to mark terminal's workspaces + console.openshift.io/terminal: "true" +spec: + started: true + routingClass: 'web-terminal' + template: + components: + - plugin: + name: web-terminal + kubernetes: + name: web-terminal + namespace: devworkspace-plugins From e735b8a6c5c893a6a95d519002ac5bf175519cfa Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 12:56:31 -0500 Subject: [PATCH 05/16] Add workaround for devfile/api issue Devfile API merge function panics if parent content is nil Signed-off-by: Angel Misevski --- pkg/library/flatten/flatten.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index 54fb7d934..61b7b29ba 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -66,7 +66,9 @@ func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, toolin pluginSpecContents = append(pluginSpecContents, &resolvedPlugin.DevWorkspaceTemplateSpecContent) } } - resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, nil, pluginSpecContents...) + // TODO: Temp workaround for issue in devfile API: can't pass in nil for parentFlattenedContent + // see: https://github.com/devfile/api/issues/295 + resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, &devworkspace.DevWorkspaceTemplateSpecContent{}, pluginSpecContents...) if err != nil { return nil, fmt.Errorf("failed to merge DevWorkspace parents/plugins: %w", err) } From 99f13bcf1cdbb3627eed1598937dec9ba9ee851d Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 13:13:52 -0500 Subject: [PATCH 06/16] Add support for plugin overrides in library Signed-off-by: Angel Misevski --- pkg/library/flatten/flatten.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index 61b7b29ba..004150c3d 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -32,7 +32,6 @@ type ResolverTools struct { // TODO: // - Implement flattening for DevWorkspace parents // - Implement plugin references by ID and URI -// - Implement plugin overrides // - Implement plugin + editor compatibility checking // - Implement cycle checking for references func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { @@ -78,14 +77,12 @@ func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, toolin } func resolvePluginComponent(name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { - if plugin.Components != nil || plugin.Commands != nil { - // TODO: Add support for overriding plugin components and commands - return nil, fmt.Errorf("plugin overrides is unsupported") - } + var resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec + var err error switch { // TODO: Add support for plugin ID and URI case plugin.Kubernetes != nil: - return resolvePluginComponentByKubernetesReference(name, plugin, tooling) + resolvedPlugin, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) case plugin.Uri != "": return nil, fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) case plugin.Id != "": @@ -93,6 +90,21 @@ func resolvePluginComponent(name string, plugin *devworkspace.PluginComponent, t default: return nil, fmt.Errorf("plugin %s does not define any resources", name) } + if err != nil { + return nil, err + } + + if plugin.Components != nil || plugin.Commands != nil { + overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedPlugin.DevWorkspaceTemplateSpecContent, devworkspace.PluginOverrides{ + Components: plugin.Components, + Commands: plugin.Commands, + }) + if err != nil { + return nil, err + } + resolvedPlugin.DevWorkspaceTemplateSpecContent = *overrideSpec + } + return resolvedPlugin, nil } func resolvePluginComponentByKubernetesReference(name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { @@ -105,6 +117,5 @@ func resolvePluginComponentByKubernetesReference(name string, plugin *devworkspa if err != nil { return nil, fmt.Errorf("failed to retrieve referenced kubernetes name and namespace for plugin %s: %w", name, err) } - return &dwTemplate.Spec, nil } From bb2237f1161a94f7a336f61be38ef9d1570789dc Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 13:57:49 -0500 Subject: [PATCH 07/16] Workaround panic error in devfile/api override code Work around panic when trying to use plugin overrides to override a devworkspace spec in devfile API. Ref: https://github.com/devfile/api/issues/296 Signed-off-by: Angel Misevski --- pkg/library/flatten/flatten.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index 004150c3d..caeb8c152 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -27,6 +27,20 @@ type ResolverTools struct { K8sClient client.Client } +// TODO: temp workaround for panic in devfile/api when using plugin overrides. See: https://github.com/devfile/api/issues/296 +type tempOverrides struct { + devworkspace.PluginOverrides +} + +func (t tempOverrides) GetToplevelLists() devworkspace.TopLevelLists { + base := t.PluginOverrides.GetToplevelLists() + base["Projects"] = []devworkspace.Keyed{} + base["StarterProjects"] = []devworkspace.Keyed{} + return base +} + +// END WORKAROUND + // ResolveDevWorkspace takes a devworkspace and returns a "resolved" version of it -- i.e. one where all plugins and parents // are inlined as components. // TODO: @@ -95,10 +109,18 @@ func resolvePluginComponent(name string, plugin *devworkspace.PluginComponent, t } if plugin.Components != nil || plugin.Commands != nil { - overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedPlugin.DevWorkspaceTemplateSpecContent, devworkspace.PluginOverrides{ - Components: plugin.Components, - Commands: plugin.Commands, + // TODO: temp workaround for panic in devfile/api when using plugin overrides. See: https://github.com/devfile/api/issues/296 + //overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedPlugin.DevWorkspaceTemplateSpecContent, devworkspace.PluginOverrides{ + // Components: plugin.Components, + // Commands: plugin.Commands, + //}) + overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedPlugin.DevWorkspaceTemplateSpecContent, tempOverrides{ + PluginOverrides: devworkspace.PluginOverrides{ + Components: plugin.Components, + Commands: plugin.Commands, + }, }) + if err != nil { return nil, err } From 22fa39e9d1edbef04c16321de05026f106bdf64b Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 17:54:51 -0500 Subject: [PATCH 08/16] Improve plugins support in flatten library * Add functionality for detecting reference cycles in plugins (e.g. plugin A references plugin B which references plugin A), avoiding the potential to lock the operator * Add basic functionality for supporting compatibility between plugins and editors, based on annotations applied to DevWorkspaceTemplates representing plugins; some work would be required for similar functionality for plugins specified by e.g. ID. This is done by building a tree of plugin information as we flatten plugin imports; each node in the tree stores the information about the plugin, a node representing the component that imported it, and a list of nodes representing plugins it imports. We can walk this tree to check e.g. plugin labels for all plugins included in the workspace (to check compatiblity) or to look for import loops (by following parent references). Signed-off-by: Angel Misevski --- pkg/library/flatten/compatibility.go | 83 +++++++++++++++++++ pkg/library/flatten/flatten.go | 64 ++++++++++---- pkg/library/flatten/helper.go | 67 +++++++++++++++ samples/plugins/machine-exec.yaml | 2 + samples/plugins/theia-next.yaml | 2 + samples/plugins/vscode-typescript.yaml | 2 + samples/plugins/web-terminal-dev.yaml | 2 + samples/plugins/web-terminal.yaml | 2 + .../with-k8s-ref/incompatible-plugins.yaml | 13 +++ 9 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 pkg/library/flatten/compatibility.go create mode 100644 pkg/library/flatten/helper.go create mode 100644 samples/with-k8s-ref/incompatible-plugins.yaml diff --git a/pkg/library/flatten/compatibility.go b/pkg/library/flatten/compatibility.go new file mode 100644 index 000000000..5b8ecf278 --- /dev/null +++ b/pkg/library/flatten/compatibility.go @@ -0,0 +1,83 @@ +// +// Copyright (c) 2019-2020 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 flatten + +import ( + "fmt" + "strings" +) + +const ( + EditorNameLabel = "devworkspace.devfile.io/editor-name" + EditorCompatibilityLabel = "devworkspace.devfile.io/editor-compatibility" +) + +func checkPluginsCompatibility(resolveCtx *resolutionContextTree) error { + editorNames, pluginReqEditors := processSubtreeCompatibility(resolveCtx) + if len(editorNames) == 0 && len(pluginReqEditors) > 0 { + var message []string + for pluginEditor, pluginComponents := range pluginReqEditors { + message = append(message, + fmt.Sprintf("Component(s) [%s] depend on editor %s", strings.Join(pluginComponents, ", "), pluginEditor)) + } + return fmt.Errorf("invalid plugins defined in devworkspace: no editor defined in workspace but %s", strings.Join(message, ". ")) + } + if len(editorNames) > 1 { + var editors []string + for editorName, editorComponent := range editorNames { + editors = append(editors, fmt.Sprintf("Component %s defines editor %s", editorComponent[0], editorName)) + } + return fmt.Errorf("devworkspace defines multiple editors: %s", strings.Join(editors, ", ")) + } + var editorName, editorComponentName string + for name, componentNames := range editorNames { + editorName = name + if len(componentNames) > 1 { + return fmt.Errorf("multiple components define the same editor: [%s]", strings.Join(componentNames, ", ")) + } + editorComponentName = componentNames[0] + } + for pluginReqEditor, pluginComponents := range pluginReqEditors { + if pluginReqEditor != editorName { + return fmt.Errorf("devworkspace uses editor %s (defined in component %s) but plugins [%s] depend on editor %s", + editorName, editorComponentName, strings.Join(pluginComponents, ", "), pluginReqEditor) + } + } + + return nil +} + +func processSubtreeCompatibility(resolveCtx *resolutionContextTree) (editors, pluginReqEditors map[string][]string) { + editors = map[string][]string{} + pluginReqEditors = map[string][]string{} + if resolveCtx.pluginMetadata != nil { + if editor := resolveCtx.pluginMetadata[EditorNameLabel]; editor != "" { + editors[editor] = append(editors[editor], resolveCtx.componentName) + } + if editorCompat := resolveCtx.pluginMetadata[EditorCompatibilityLabel]; editorCompat != "" { + pluginReqEditors[editorCompat] = append(pluginReqEditors[editorCompat], resolveCtx.componentName) + } + } + for _, plugin := range resolveCtx.plugins { + subEditors, subPluginReqEditors := processSubtreeCompatibility(plugin) + mergeMaps(subEditors, editors) + mergeMaps(subPluginReqEditors, pluginReqEditors) + } + return editors, pluginReqEditors +} + +func mergeMaps(from, into map[string][]string) { + for k, v := range from { + into[k] = append(into[k], v...) + } +} diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index caeb8c152..0645e8ba3 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -18,6 +18,7 @@ import ( devworkspace "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" "github.com/devfile/api/pkg/utils/overriding" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -46,9 +47,20 @@ func (t tempOverrides) GetToplevelLists() devworkspace.TopLevelLists { // TODO: // - Implement flattening for DevWorkspace parents // - Implement plugin references by ID and URI -// - Implement plugin + editor compatibility checking -// - Implement cycle checking for references func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { + resolutionCtx := &resolutionContextTree{} + resolvedDW, err := recursiveResolve(workspace, tooling, resolutionCtx) + if err != nil { + return nil, err + } + err = checkPluginsCompatibility(resolutionCtx) + if err != nil { + return nil, err + } + return resolvedDW, nil +} + +func recursiveResolve(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools, resolveCtx *resolutionContextTree) (*devworkspace.DevWorkspaceTemplateSpec, error) { if DevWorkspaceIsFlattened(workspace) { return workspace.DeepCopy(), nil } @@ -56,6 +68,7 @@ func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, toolin // TODO: Add support for flattening DevWorkspace parents return nil, fmt.Errorf("DevWorkspace parent is unsupported") } + resolvedContent := &devworkspace.DevWorkspaceTemplateSpecContent{} resolvedContent.Projects = workspace.Projects resolvedContent.StarterProjects = workspace.StarterProjects @@ -68,44 +81,53 @@ func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, toolin // No action necessary resolvedContent.Components = append(resolvedContent.Components, component) } else { - pluginComponent, err := resolvePluginComponent(component.Name, component.Plugin, tooling) + pluginComponent, pluginMeta, err := resolvePluginComponent(component.Name, component.Plugin, tooling) if err != nil { return nil, err } - resolvedPlugin, err := ResolveDevWorkspace(*pluginComponent, tooling) + newCtx := resolveCtx.addPlugin(component.Name, component.Plugin) + newCtx.pluginMetadata = pluginMeta + if err := newCtx.hasCycle(); err != nil { + return nil, err + } + + resolvedPlugin, err := recursiveResolve(*pluginComponent, tooling, newCtx) if err != nil { return nil, err } pluginSpecContents = append(pluginSpecContents, &resolvedPlugin.DevWorkspaceTemplateSpecContent) } } + // TODO: Temp workaround for issue in devfile API: can't pass in nil for parentFlattenedContent // see: https://github.com/devfile/api/issues/295 resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, &devworkspace.DevWorkspaceTemplateSpecContent{}, pluginSpecContents...) if err != nil { return nil, fmt.Errorf("failed to merge DevWorkspace parents/plugins: %w", err) } + return &devworkspace.DevWorkspaceTemplateSpec{ DevWorkspaceTemplateSpecContent: *resolvedContent, }, nil } -func resolvePluginComponent(name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { - var resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec - var err error +func resolvePluginComponent( + name string, + plugin *devworkspace.PluginComponent, + tooling ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, pluginMeta map[string]string, err error) { switch { // TODO: Add support for plugin ID and URI case plugin.Kubernetes != nil: - resolvedPlugin, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) + resolvedPlugin, pluginMeta, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) case plugin.Uri != "": - return nil, fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) + err = fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) case plugin.Id != "": - return nil, fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) + err = fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) default: - return nil, fmt.Errorf("plugin %s does not define any resources", name) + err = fmt.Errorf("plugin %s does not define any resources", name) } if err != nil { - return nil, err + return nil, nil, err } if plugin.Components != nil || plugin.Commands != nil { @@ -122,22 +144,28 @@ func resolvePluginComponent(name string, plugin *devworkspace.PluginComponent, t }) if err != nil { - return nil, err + return nil, nil, err } resolvedPlugin.DevWorkspaceTemplateSpecContent = *overrideSpec } - return resolvedPlugin, nil + return resolvedPlugin, pluginMeta, nil } -func resolvePluginComponentByKubernetesReference(name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { +func resolvePluginComponentByKubernetesReference( + name string, + plugin *devworkspace.PluginComponent, + tooling ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, pluginLabels map[string]string, err error) { var dwTemplate devworkspace.DevWorkspaceTemplate namespacedName := types.NamespacedName{ Name: plugin.Kubernetes.Name, Namespace: plugin.Kubernetes.Namespace, } - err := tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) + err = tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) if err != nil { - return nil, fmt.Errorf("failed to retrieve referenced kubernetes name and namespace for plugin %s: %w", name, err) + if errors.IsNotFound(err) { + return nil, nil, fmt.Errorf("plugin for component %s not found", name) + } + return nil, nil, fmt.Errorf("failed to retrieve plugin referenced by kubernetes name and namespace '%s': %w", name, err) } - return &dwTemplate.Spec, nil + return &dwTemplate.Spec, dwTemplate.Labels, nil } diff --git a/pkg/library/flatten/helper.go b/pkg/library/flatten/helper.go new file mode 100644 index 000000000..c1e25d634 --- /dev/null +++ b/pkg/library/flatten/helper.go @@ -0,0 +1,67 @@ +// +// Copyright (c) 2019-2020 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 flatten + +import ( + "fmt" + "reflect" + + devworkspace "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" +) + +// resolutionContextTree is a recursive structure representing information about the devworkspace that is +// lost when flattening +type resolutionContextTree struct { + componentName string + importReference devworkspace.ImportReference + pluginMetadata map[string]string + plugins []*resolutionContextTree + parentNode *resolutionContextTree +} + +func (t *resolutionContextTree) addPlugin(name string, plugin *devworkspace.PluginComponent) *resolutionContextTree { + newNode := &resolutionContextTree{ + componentName: name, + importReference: plugin.ImportReference, + parentNode: t, + } + t.plugins = append(t.plugins, newNode) + return newNode +} + +func (t *resolutionContextTree) hasCycle() error { + var seenRefs []devworkspace.ImportReference + currNode := t + for currNode.parentNode != nil { + for _, seenRef := range seenRefs { + if reflect.DeepEqual(seenRef, currNode.importReference) { + return fmt.Errorf("DevWorkspace has an cycle in references: %s", formatImportCycle(t)) + } + } + seenRefs = append(seenRefs, currNode.importReference) + currNode = currNode.parentNode + } + return nil +} + +func formatImportCycle(end *resolutionContextTree) string { + cycle := fmt.Sprintf("%s", end.componentName) + for end.parentNode != nil { + end = end.parentNode + if end.parentNode == nil { + end.componentName = "devworkspace" + } + cycle = fmt.Sprintf("%s -> %s", end.componentName, cycle) + } + return cycle +} diff --git a/samples/plugins/machine-exec.yaml b/samples/plugins/machine-exec.yaml index 53ca3b432..28064f43d 100644 --- a/samples/plugins/machine-exec.yaml +++ b/samples/plugins/machine-exec.yaml @@ -2,6 +2,8 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: machine-exec + labels: + "devworkspace.devfile.io/editor-compatibility": "che-theia" spec: components: - name: che-machine-exec diff --git a/samples/plugins/theia-next.yaml b/samples/plugins/theia-next.yaml index f9b011fd6..8c3e9a6c7 100644 --- a/samples/plugins/theia-next.yaml +++ b/samples/plugins/theia-next.yaml @@ -2,6 +2,8 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: theia-next + labels: + "devworkspace.devfile.io/editor-name": "che-theia" spec: components: - name: plugins diff --git a/samples/plugins/vscode-typescript.yaml b/samples/plugins/vscode-typescript.yaml index 6f9571747..f423aba8d 100644 --- a/samples/plugins/vscode-typescript.yaml +++ b/samples/plugins/vscode-typescript.yaml @@ -2,6 +2,8 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: vscode-typescript + labels: + "devworkspace.devfile.io/editor-compatibility": "che-theia" spec: components: - name: sidecar-typescript diff --git a/samples/plugins/web-terminal-dev.yaml b/samples/plugins/web-terminal-dev.yaml index 628d316d6..17cc12ab5 100644 --- a/samples/plugins/web-terminal-dev.yaml +++ b/samples/plugins/web-terminal-dev.yaml @@ -2,6 +2,8 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: web-terminal-dev + labels: + "devworkspace.devfile.io/editor-name": "web-terminal" spec: components: - name: web-terminal diff --git a/samples/plugins/web-terminal.yaml b/samples/plugins/web-terminal.yaml index bc9d0500d..01fff49f0 100644 --- a/samples/plugins/web-terminal.yaml +++ b/samples/plugins/web-terminal.yaml @@ -2,6 +2,8 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: web-terminal + labels: + "devworkspace.devfile.io/editor-name": "web-terminal" spec: components: - name: web-terminal diff --git a/samples/with-k8s-ref/incompatible-plugins.yaml b/samples/with-k8s-ref/incompatible-plugins.yaml new file mode 100644 index 000000000..248ae603d --- /dev/null +++ b/samples/with-k8s-ref/incompatible-plugins.yaml @@ -0,0 +1,13 @@ +kind: DevWorkspace +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: incompatible-plugins +spec: + started: true + template: + components: + - name: terminal + plugin: + kubernetes: + name: machine-exec + namespace: devworkspace-plugins From 01c479d3d7c71000981db2e74c0bf7d4020ad38a Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 11 Jan 2021 23:39:47 -0500 Subject: [PATCH 09/16] Add test cases for plugin resolution in library code. Signed-off-by: Angel Misevski --- pkg/library/flatten/flatten_test.go | 152 ++++++ .../flatten/testdata/already-flattened.yaml | 71 +++ .../testdata/error_bad-plugin-merge.yaml | 23 + .../testdata/error_conflicting-merge.yaml | 22 + .../testdata/error_duplicate-editors.yaml | 41 ++ .../error_error-when-retrieving-plugin.yaml | 15 + .../flatten/testdata/error_has-parent.yaml | 15 + .../testdata/error_multiple-editors.yaml | 41 ++ .../error_plugin-needs-missing-editor.yaml | 26 + .../testdata/error_plugin-not-found.yaml | 16 + .../testdata/error_plugin-references-self.yml | 25 + .../testdata/error_plugins-have-cycle.yml | 37 ++ .../testdata/error_plugins-incompatible.yaml | 42 ++ .../flatten/testdata/nodejs-workspace.yaml | 510 ++++++++++++++++++ .../testdata/web-terminal-with-plugin.yaml | 82 +++ 15 files changed, 1118 insertions(+) create mode 100644 pkg/library/flatten/flatten_test.go create mode 100644 pkg/library/flatten/testdata/already-flattened.yaml create mode 100644 pkg/library/flatten/testdata/error_bad-plugin-merge.yaml create mode 100644 pkg/library/flatten/testdata/error_conflicting-merge.yaml create mode 100644 pkg/library/flatten/testdata/error_duplicate-editors.yaml create mode 100644 pkg/library/flatten/testdata/error_error-when-retrieving-plugin.yaml create mode 100644 pkg/library/flatten/testdata/error_has-parent.yaml create mode 100644 pkg/library/flatten/testdata/error_multiple-editors.yaml create mode 100644 pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml create mode 100644 pkg/library/flatten/testdata/error_plugin-not-found.yaml create mode 100644 pkg/library/flatten/testdata/error_plugin-references-self.yml create mode 100644 pkg/library/flatten/testdata/error_plugins-have-cycle.yml create mode 100644 pkg/library/flatten/testdata/error_plugins-incompatible.yaml create mode 100644 pkg/library/flatten/testdata/nodejs-workspace.yaml create mode 100644 pkg/library/flatten/testdata/web-terminal-with-plugin.yaml diff --git a/pkg/library/flatten/flatten_test.go b/pkg/library/flatten/flatten_test.go new file mode 100644 index 000000000..1f87f6649 --- /dev/null +++ b/pkg/library/flatten/flatten_test.go @@ -0,0 +1,152 @@ +// +// Copyright (c) 2019-2020 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 flatten + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + devworkspace "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" +) + +var workspaceTemplateDiffOpts = cmp.Options{ + cmpopts.SortSlices(func(a, b devworkspace.Component) bool { + return strings.Compare(a.Key(), b.Key()) > 0 + }), + cmpopts.SortSlices(func(a, b string) bool { + return strings.Compare(a, b) > 0 + }), + // TODO: Devworkspace overriding results in empty []string instead of nil + cmpopts.IgnoreFields(devworkspace.WorkspaceEvents{}, "PostStart", "PreStop", "PostStop"), +} + +type testCase struct { + Name string `json:"name"` + Input testInput `json:"input"` + Output testOutput `json:"output"` +} + +type testInput struct { + Workspace devworkspace.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` + // Plugins is a map of plugin "name" to devworkspace template; namespace is ignored. + Plugins map[string]devworkspace.DevWorkspaceTemplate `json:"plugins,omitempty"` + // Errors is a map of plugin name to the error that should be returned when attempting to retrieve it. + Errors map[string]testPluginError `json:"errors,omitempty"` +} + +type testPluginError struct { + IsNotFound bool `json:"isNotFound"` + Message string `json:"message"` +} + +type testOutput struct { + Workspace *devworkspace.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` + ErrRegexp *string `json:"errRegexp,omitempty"` +} + +type fakeK8sClient struct { + client.Client // To satisfy interface; override all used methods + plugins map[string]devworkspace.DevWorkspaceTemplate + errors map[string]testPluginError +} + +func (client *fakeK8sClient) Get(_ context.Context, namespacedName client.ObjectKey, obj runtime.Object) error { + template, ok := obj.(*devworkspace.DevWorkspaceTemplate) + if !ok { + return fmt.Errorf("Called Get() in fake client with non-DevWorkspaceTemplate") + } + if plugin, ok := client.plugins[namespacedName.Name]; ok { + *template = plugin + return nil + } + if err, ok := client.errors[namespacedName.Name]; ok { + if err.IsNotFound { + return k8sErrors.NewNotFound(schema.GroupResource{}, namespacedName.Name) + } else { + return errors.New(err.Message) + } + } + return fmt.Errorf("test does not define an entry for plugin %s", namespacedName.Name) +} + +func loadTestCaseOrPanic(t *testing.T, testFilename string) testCase { + testPath := filepath.Join("./testdata", testFilename) + bytes, err := ioutil.ReadFile(testPath) + if err != nil { + t.Fatal(err) + } + var test testCase + if err := yaml.Unmarshal(bytes, &test); err != nil { + t.Fatal(err) + } + t.Log(fmt.Sprintf("Read file:\n%+v\n\n", test)) + return test +} + +func loadAllTestsOrPanic(t *testing.T) []testCase { + files, err := ioutil.ReadDir("./testdata") + if err != nil { + t.Fatal(err) + } + var tests []testCase + for _, file := range files { + if file.IsDir() { + continue + } + tests = append(tests, loadTestCaseOrPanic(t, file.Name())) + } + return tests +} + +func TestResolveDevWorkspace(t *testing.T) { + tests := loadAllTestsOrPanic(t) + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testClient := &fakeK8sClient{ + plugins: tt.Input.Plugins, + errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testClient, + } + outputWorkspace, err := ResolveDevWorkspace(tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, workspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, workspaceTemplateDiffOpts)) + } + }) + } +} diff --git a/pkg/library/flatten/testdata/already-flattened.yaml b/pkg/library/flatten/testdata/already-flattened.yaml new file mode 100644 index 000000000..b4eaedf85 --- /dev/null +++ b/pkg/library/flatten/testdata/already-flattened.yaml @@ -0,0 +1,71 @@ +name: "Already flattened workspace" + +input: + workspace: + components: + - name: dev + container: + image: quay.io/wto/web-terminal-tooling:latest + mountSources: false + memoryLimit: 256Mi + args: ["tail", "-f", "/dev/null"] + env: + - value: '\[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\]' + name: PS1 + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + mountSources: false + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + cookiesAuthEnabled: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" + +output: + workspace: + components: + - name: dev + container: + image: quay.io/wto/web-terminal-tooling:latest + mountSources: false + memoryLimit: 256Mi + args: ["tail", "-f", "/dev/null"] + env: + - value: '\[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\]' + name: PS1 + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + mountSources: false + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + cookiesAuthEnabled: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" diff --git a/pkg/library/flatten/testdata/error_bad-plugin-merge.yaml b/pkg/library/flatten/testdata/error_bad-plugin-merge.yaml new file mode 100644 index 000000000..97df12ef4 --- /dev/null +++ b/pkg/library/flatten/testdata/error_bad-plugin-merge.yaml @@ -0,0 +1,23 @@ +name: "Attempting to override undefined plugin component" + +input: + workspace: + components: + - name: "bad-override" + plugin: + kubernetes: + name: override + components: + - name: non-existent + container: + memoryLimit: 512Mi + plugins: + override: + spec: + components: + - name: my-component + container: + image: test-image + +output: + errRegexp: "Some Components do not override any existing element: non-existent.*" diff --git a/pkg/library/flatten/testdata/error_conflicting-merge.yaml b/pkg/library/flatten/testdata/error_conflicting-merge.yaml new file mode 100644 index 000000000..a41d0ea09 --- /dev/null +++ b/pkg/library/flatten/testdata/error_conflicting-merge.yaml @@ -0,0 +1,22 @@ +name: "Component conflicts with plugin component" + +input: + workspace: + components: + - name: "component-conflict" + plugin: + kubernetes: + name: test-plugin + - name: my-component + container: + image: test-image + plugins: + test-plugin: + spec: + components: + - name: my-component + container: + image: test-image + +output: + errRegexp: "Some Components are already defined in plugin '.*': my-component.*" diff --git a/pkg/library/flatten/testdata/error_duplicate-editors.yaml b/pkg/library/flatten/testdata/error_duplicate-editors.yaml new file mode 100644 index 000000000..e510e86e9 --- /dev/null +++ b/pkg/library/flatten/testdata/error_duplicate-editors.yaml @@ -0,0 +1,41 @@ +name: "Plugins incompatible with editor" + +input: + workspace: + components: + - name: editor-plugin-1 + plugin: + kubernetes: + name: editor-plugin-1 + - name: editor-plugin-2 + plugin: + kubernetes: + name: editor-plugin-2 + plugins: + editor-plugin-1: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: editor-plugin-1 + labels: + "devworkspace.devfile.io/editor-name": "test-editor-1" + spec: + components: + - name: editor-container + container: + image: "test-image" + editor-plugin-2: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-sidecar + labels: + "devworkspace.devfile.io/editor-name": "test-editor-1" + spec: + components: + - name: plugin-container + container: + image: "test-image" + +output: + errRegexp: 'multiple components define the same editor.*' diff --git a/pkg/library/flatten/testdata/error_error-when-retrieving-plugin.yaml b/pkg/library/flatten/testdata/error_error-when-retrieving-plugin.yaml new file mode 100644 index 000000000..ef4551bfa --- /dev/null +++ b/pkg/library/flatten/testdata/error_error-when-retrieving-plugin.yaml @@ -0,0 +1,15 @@ +name: "Error retrieving plugin" + +input: + workspace: + components: + - name: "bad-plugin" + plugin: + kubernetes: + name: test-plugin + errors: + test-plugin: + message: "Internal k8s error" + +output: + errRegexp: ".*failed to retrieve.*bad-plugin.*Internal k8s error.*" diff --git a/pkg/library/flatten/testdata/error_has-parent.yaml b/pkg/library/flatten/testdata/error_has-parent.yaml new file mode 100644 index 000000000..36031cee0 --- /dev/null +++ b/pkg/library/flatten/testdata/error_has-parent.yaml @@ -0,0 +1,15 @@ +name: "Workspace has parent" + +input: + workspace: + parent: + kubernetes: + name: my-parent + components: + - name: my-component + container: + image: test-image + + +output: + errRegexp: "DevWorkspace parent is unsupported" diff --git a/pkg/library/flatten/testdata/error_multiple-editors.yaml b/pkg/library/flatten/testdata/error_multiple-editors.yaml new file mode 100644 index 000000000..7de46b270 --- /dev/null +++ b/pkg/library/flatten/testdata/error_multiple-editors.yaml @@ -0,0 +1,41 @@ +name: "Plugins incompatible with editor" + +input: + workspace: + components: + - name: editor-plugin-1 + plugin: + kubernetes: + name: editor-plugin-1 + - name: editor-plugin-2 + plugin: + kubernetes: + name: editor-plugin-2 + plugins: + editor-plugin-1: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: editor-plugin-1 + labels: + "devworkspace.devfile.io/editor-name": "test-editor-1" + spec: + components: + - name: editor-container + container: + image: "test-image" + editor-plugin-2: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-sidecar + labels: + "devworkspace.devfile.io/editor-name": "test-editor-2" + spec: + components: + - name: plugin-container + container: + image: "test-image" + +output: + errRegexp: 'devworkspace defines multiple editors.*' diff --git a/pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml b/pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml new file mode 100644 index 000000000..ed9500ffa --- /dev/null +++ b/pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml @@ -0,0 +1,26 @@ +name: "Plugins incompatible with editor" + +input: + workspace: + components: + - name: my-plugin + plugin: + kubernetes: + name: my-plugin + plugins: + my-plugin: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-sidecar + labels: + "devworkspace.devfile.io/editor-compatibility": "my-editor" + spec: + components: + - name: plugin-container + container: + image: "test-image" + +output: + errRegexp: 'invalid plugins defined in devworkspace: no editor defined + in workspace but Component\(s\) \[my-plugin\] depend on editor my-editor' diff --git a/pkg/library/flatten/testdata/error_plugin-not-found.yaml b/pkg/library/flatten/testdata/error_plugin-not-found.yaml new file mode 100644 index 000000000..77aef485d --- /dev/null +++ b/pkg/library/flatten/testdata/error_plugin-not-found.yaml @@ -0,0 +1,16 @@ +name: "Referenced plugin cannot be found" + +input: + workspace: + components: + - name: "bad-plugin" + plugin: + kubernetes: + name: test-plugin + errors: + test-plugin: + isNotFound: true + message: "Plugin not found" + +output: + errRegexp: "plugin for component bad-plugin not found.*" diff --git a/pkg/library/flatten/testdata/error_plugin-references-self.yml b/pkg/library/flatten/testdata/error_plugin-references-self.yml new file mode 100644 index 000000000..c551e576e --- /dev/null +++ b/pkg/library/flatten/testdata/error_plugin-references-self.yml @@ -0,0 +1,25 @@ +name: "Plugin references self" + +input: + workspace: + components: + - name: "plugin-a" + plugin: + kubernetes: + name: plugin-a + plugins: + plugin-a: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-a + spec: + components: + - name: plugin-a + plugin: + kubernetes: + name: plugin-a + namespace: devworkspace-plugins + +output: + errRegexp: "DevWorkspace has an cycle in references.*" diff --git a/pkg/library/flatten/testdata/error_plugins-have-cycle.yml b/pkg/library/flatten/testdata/error_plugins-have-cycle.yml new file mode 100644 index 000000000..9a8f8ee08 --- /dev/null +++ b/pkg/library/flatten/testdata/error_plugins-have-cycle.yml @@ -0,0 +1,37 @@ +name: "Plugins have reference cycle" + +input: + workspace: + components: + - name: "plugin-a" + plugin: + kubernetes: + name: plugin-a + plugins: + plugin-a: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-a + spec: + components: + - name: plugin-b + plugin: + kubernetes: + name: plugin-b + namespace: devworkspace-plugins + plugin-b: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-b + spec: + components: + - name: plugin-a + plugin: + kubernetes: + name: plugin-a + namespace: devworkspace-plugins + +output: + errRegexp: "DevWorkspace has an cycle in references.*" diff --git a/pkg/library/flatten/testdata/error_plugins-incompatible.yaml b/pkg/library/flatten/testdata/error_plugins-incompatible.yaml new file mode 100644 index 000000000..91942950b --- /dev/null +++ b/pkg/library/flatten/testdata/error_plugins-incompatible.yaml @@ -0,0 +1,42 @@ +name: "Plugins incompatible with editor" + +input: + workspace: + components: + - name: editor-plugin + plugin: + kubernetes: + name: editor-plugin + - name: plugin-for-different-editor + plugin: + kubernetes: + name: plugin-for-different-editor + plugins: + editor-plugin: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: editor-plugin + labels: + "devworkspace.devfile.io/editor-name": "test-editor" + spec: + components: + - name: editor-container + container: + image: "test-image" + plugin-for-different-editor: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-sidecar + labels: + "devworkspace.devfile.io/editor-compatibility": "another-editor" + spec: + components: + - name: plugin-container + container: + image: "test-image" + +output: + errRegexp: 'devworkspace uses editor test-editor \(defined in component editor-plugin\) + but plugins \[plugin-for-different-editor\] depend on editor another-editor' diff --git a/pkg/library/flatten/testdata/nodejs-workspace.yaml b/pkg/library/flatten/testdata/nodejs-workspace.yaml new file mode 100644 index 000000000..33269095c --- /dev/null +++ b/pkg/library/flatten/testdata/nodejs-workspace.yaml @@ -0,0 +1,510 @@ +name: "Theia and NodeJS plugin workspace" + +input: + workspace: + projects: + - name: web-nodejs-sample + git: + remotes: + origin: "https://github.com/che-samples/web-nodejs-sample.git" + components: + - name: che-theia + plugin: + kubernetes: + name: theia-next + namespace: devworkspace-plugins + - name: machine-exec + plugin: + kubernetes: + name: machine-exec + namespace: devworkspace-plugins + - name: typescript + plugin: + kubernetes: + name: vscode-typescript + namespace: devworkspace-plugins + components: + - name: sidecar-typescript + container: + memoryLimit: 512Mi + - name: nodejs + container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + memoryLimit: 512Mi + endpoints: + - name: nodejs + protocol: http + targetPort: 3000 + mountSources: true + commands: + - id: download-dependencies + exec: + component: nodejs + commandLine: npm install + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app + exec: + component: nodejs + commandLine: nodemon app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app-with-debugging-enabled + exec: + component: nodejs + commandLine: nodemon --inspect app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: stop-the-app + exec: + component: nodejs + commandLine: >- + node_server_pids=$(pgrep -fx '.*nodemon (--inspect )?app.js' | tr "\\n" " ") && + echo "Stopping node server with PIDs: ${node_server_pids}" && + kill -15 ${node_server_pids} &>/dev/null && echo 'Done.' + - id: attach-remote-debugger + vscodeLaunch: + inlined: | + { + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Remote", + "address": "localhost", + "port": 9229, + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}" + } + ] + } + plugins: + theia-next: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: theia-next + labels: + "devworkspace.devfile.io/editor-name": "che-theia" + spec: + components: + - name: plugins + volume: {} + - name: remote-endpoint + volume: {} # TODO: Fix this once ephemeral volumes are supported + - name: vsx-installer # Mainly reads the container objects and searches for those + # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls + # Those found in the dedicated containers components are with a sidecar, + # Those found in the che-theia container are without a sidecar. + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: + args: + - /bin/sh + - '-c' + - | + KUBE_API_ENDPOINT="https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}" &&\ + TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) &&\ + WORKSPACE=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer ${TOKEN}" $KUBE_API_ENDPOINT) &&\ + for container in $(echo $WORKSPACE | sed -e 's;[[,]\({"attributes":{"app.kubernetes.io\);\n\1;g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do \ + dest=$(echo "$container" | sed 's;.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*;\1;' - ) ;\ + urls=$(echo "$container" | sed 's;.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*;\1;' - ) ;\ + mkdir -p $dest ;\ + for url in $(echo $urls | sed 's/[",]/ /g' - ); do \ + echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url) ;\ + done \ + done + image: 'quay.io/samsahai/curl:latest' + volumeMounts: + - path: "/plugins" + name: plugins + - name: remote-runtime-injector + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: #### corresponds to `initContainer` definition in old meta.yaml. + image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + - name: theia-ide + attributes: + "app.kubernetes.io/name": che-theia.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": editor + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the vscode-pull-request-github vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://github.com/microsoft/vscode-pull-request-github/releases/download/v0.8.0/vscode-pull-request-github-0.8.0.vsix + container: + image: "quay.io/eclipse/che-theia:next" + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: "3130" + - name: THEIA_HOST + value: 0.0.0.0 + volumeMounts: + - path: "/plugins" + name: plugins + mountSources: true + memoryLimit: "512M" + endpoints: + - name: "theia" + exposure: public + targetPort: 3100 + secure: true + protocol: http + attributes: + type: ide + cookiesAuthEnabled: "true" + - name: "webviews" + exposure: public + targetPort: 3100 + protocol: http + secure: true + attributes: + type: webview + cookiesAuthEnabled: "true" + unique: "true" + - name: "theia-dev" + exposure: public + targetPort: 3130 + protocol: http + attributes: + type: ide-dev + - name: "theia-redir-1" + exposure: public + targetPort: 13131 + protocol: http + - name: "theia-redir-2" + exposure: public + targetPort: 13132 + protocol: http + - name: "theia-redir-3" + exposure: public + targetPort: 13133 + protocol: http + commands: + # Commands coming from plugin editor + - id: inject-theia-in-remote-sidecar + apply: + component: remote-runtime-injector + - id: copy-vsx + apply: + component: vsx-installer + events: + preStart: + - inject-theia-in-remote-sidecar + - copy-vsx + + machine-exec: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: machine-exec + labels: + "devworkspace.devfile.io/editor-compatibility": "che-theia" + spec: + components: + - name: che-machine-exec + attributes: + "app.kubernetes.io/name": che-terminal.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": terminal + container: + image: "quay.io/eclipse/che-machine-exec:7.20.0" + command: ['/go/bin/che-machine-exec'] + args: + - '--url' + - '0.0.0.0:4444' + - '--pod-selector' + - controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID) + endpoints: + - name: "che-mach-exec" + exposure: public + targetPort: 4444 + protocol: ws + secure: true + attributes: + type: terminal + cookiesAuthEnabled: "true" + + vscode-typescript: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: vscode-typescript + labels: + "devworkspace.devfile.io/editor-compatibility": "che-theia" + spec: + components: + - name: sidecar-typescript + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": vscode-plugin + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the typescript vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://download.jboss.org/jbosstools/vscode/3rdparty/ms-code.typescript/che-typescript-language-1.35.1.vsix + + container: + image: "quay.io/eclipse/che-sidecar-node:10-0cb5d78" + memoryLimit: '512Mi' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/vscode-typescript + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + - name: plugins + path: /plugins + + +output: + workspace: + projects: + - name: web-nodejs-sample + git: + remotes: + origin: "https://github.com/che-samples/web-nodejs-sample.git" + + components: + + - name: theia-ide + attributes: + "app.kubernetes.io/name": che-theia.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": editor + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the vscode-pull-request-github vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://github.com/microsoft/vscode-pull-request-github/releases/download/v0.8.0/vscode-pull-request-github-0.8.0.vsix + + container: + image: "quay.io/eclipse/che-theia:next" + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: "3130" + - name: THEIA_HOST + value: 0.0.0.0 + volumeMounts: + - path: "/plugins" + name: plugins + mountSources: true + memoryLimit: "512M" + endpoints: + - name: "theia" + exposure: public + targetPort: 3100 + secure: true + protocol: http + attributes: + type: ide + cookiesAuthEnabled: "true" + - name: "webviews" + exposure: public + targetPort: 3100 + protocol: http + secure: true + attributes: + type: webview + cookiesAuthEnabled: "true" + unique: "true" + - name: "theia-dev" + exposure: public + targetPort: 3130 + protocol: http + attributes: + type: ide-dev + - name: "theia-redir-1" + exposure: public + targetPort: 13131 + protocol: http + - name: "theia-redir-2" + exposure: public + targetPort: 13132 + protocol: http + - name: "theia-redir-3" + exposure: public + targetPort: 13133 + protocol: http + + - name: plugins + volume: {} + + - name: vsx-installer # Mainly reads the container objects and searches for those + # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls + # Those found in the dedicated containers components are with a sidecar, + # Those found in the che-theia container are without a sidecar. + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: + args: + - /bin/sh + - '-c' + - | + KUBE_API_ENDPOINT="https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}" &&\ + TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) &&\ + WORKSPACE=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer ${TOKEN}" $KUBE_API_ENDPOINT) &&\ + for container in $(echo $WORKSPACE | sed -e 's;[[,]\({"attributes":{"app.kubernetes.io\);\n\1;g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do \ + dest=$(echo "$container" | sed 's;.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*;\1;' - ) ;\ + urls=$(echo "$container" | sed 's;.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*;\1;' - ) ;\ + mkdir -p $dest ;\ + for url in $(echo $urls | sed 's/[",]/ /g' - ); do \ + echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url) ;\ + done \ + done + image: 'quay.io/samsahai/curl:latest' + volumeMounts: + - path: "/plugins" + name: plugins + + - name: remote-endpoint + volume: {} + # ephemeral: true #### We should add it in the Devfile 2.0 spec ! Not critical to implement at start though + + - name: remote-runtime-injector + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: #### corresponds to `initContainer` definition in old meta.yaml. + image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + + - name: che-machine-exec + attributes: + "app.kubernetes.io/name": che-terminal.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": terminal + container: + image: "quay.io/eclipse/che-machine-exec:7.20.0" + command: ['/go/bin/che-machine-exec'] + args: + - '--url' + - '0.0.0.0:4444' + - '--pod-selector' + - controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID) + endpoints: + - name: "che-mach-exec" + exposure: public + targetPort: 4444 + protocol: ws + secure: true + attributes: + type: terminal + cookiesAuthEnabled: "true" + - name: sidecar-typescript + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": vscode-plugin + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the typescript vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://download.jboss.org/jbosstools/vscode/3rdparty/ms-code.typescript/che-typescript-language-1.35.1.vsix + + container: + image: "quay.io/eclipse/che-sidecar-node:10-0cb5d78" + memoryLimit: '512Mi' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/vscode-typescript + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + - name: plugins + path: /plugins + + # User runtime container + - name: nodejs + container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + memoryLimit: 512Mi + endpoints: + - name: nodejs + protocol: http + targetPort: 3000 + mountSources: true + + commands: + + # Commands coming from plugin editor + - id: inject-theia-in-remote-sidecar + apply: + component: remote-runtime-injector + - id: copy-vsx + apply: + component: vsx-installer + + # User commands + - id: download-dependencies + exec: + component: nodejs + commandLine: npm install + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app + exec: + component: nodejs + commandLine: nodemon app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app-with-debugging-enabled + exec: + component: nodejs + commandLine: nodemon --inspect app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: stop-the-app + exec: + component: nodejs + commandLine: >- + node_server_pids=$(pgrep -fx '.*nodemon (--inspect )?app.js' | tr "\\n" " ") && + echo "Stopping node server with PIDs: ${node_server_pids}" && + kill -15 ${node_server_pids} &>/dev/null && echo 'Done.' + - id: attach-remote-debugger + vscodeLaunch: + inlined: | + { + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Remote", + "address": "localhost", + "port": 9229, + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}" + } + ] + } + + events: + preStart: + - inject-theia-in-remote-sidecar + - copy-vsx diff --git a/pkg/library/flatten/testdata/web-terminal-with-plugin.yaml b/pkg/library/flatten/testdata/web-terminal-with-plugin.yaml new file mode 100644 index 000000000..a914b642a --- /dev/null +++ b/pkg/library/flatten/testdata/web-terminal-with-plugin.yaml @@ -0,0 +1,82 @@ +name: "Web terminal default" + +input: + workspace: + components: + - name: dev + container: + memoryLimit: "256Mi" + image: quay.io/wto/web-terminal-tooling:latest + args: ["tail", "-f", "/dev/null"] + env: + - name: PS1 + value: \[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\] + - name: web-terminal + plugin: + kubernetes: + name: web-terminal + namespace: devworkspace-plugins + plugins: + web-terminal: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: web-terminal + labels: + "devworkspace.devfile.io/editor-name": "web-terminal" + spec: + components: + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + cookiesAuthEnabled: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" + +output: + workspace: + components: + - name: dev + container: + image: quay.io/wto/web-terminal-tooling:latest + memoryLimit: 256Mi + args: ["tail", "-f", "/dev/null"] + env: + - value: '\[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\]' + name: PS1 + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/workspace_id=$(CHE_WORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + cookiesAuthEnabled: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" From 83f5133665add62119d7f494046b0d865d981a07 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 14 Jan 2021 13:53:32 -0500 Subject: [PATCH 10/16] Add DevWorkspaceTemplates get/create/update to workspace RBAC This functionality is required for e.g. Theia to be able to create DevWorkspaceTemplates and import them into the current workspace. Signed-off-by: Angel Misevski --- controllers/workspace/provision/rbac.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/controllers/workspace/provision/rbac.go b/controllers/workspace/provision/rbac.go index d20cee6c1..75524dad8 100644 --- a/controllers/workspace/provision/rbac.go +++ b/controllers/workspace/provision/rbac.go @@ -57,7 +57,12 @@ func generateRBAC(namespace string) []runtime.Object { { Resources: []string{"devworkspaces"}, APIGroups: []string{"workspace.devfile.io"}, - Verbs: []string{"patch", "get"}, + Verbs: []string{"patch", "get", "update"}, + }, + { + Resources: []string{"devworkspacetemplates"}, + APIGroups: []string{"workspace.devfile.io"}, + Verbs: []string{"get", "create", "patch", "update", "delete", "list", "watch"}, }, }, }, From 9e01806340e17fe3d3cb85d9117e1ed70634f14b Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 14 Jan 2021 17:20:47 -0500 Subject: [PATCH 11/16] Look for plugins in the current namespace if namespace is empty If a DevWorkspace specifies a plugin via Kubernetes reference and does not include a namespace, look in the DevWorkspace's namespace by default. Signed-off-by: Angel Misevski --- controllers/workspace/devworkspace_controller.go | 5 +++-- pkg/library/flatten/flatten.go | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index ad2deacae..2153e04e2 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -177,8 +177,9 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct // TODO#185 : Temporarily do devfile flattening in main reconcile loop; this should be moved to a subcontroller. // TODO#185 : Implement defaulting container component for Web Terminals for compatibility flattenHelpers := flatten.ResolverTools{ - Context: ctx, - K8sClient: r.Client, + InstanceNamespace: workspace.Namespace, + Context: ctx, + K8sClient: r.Client, } flattenedWorkspace, err := flatten.ResolveDevWorkspace(workspace.Spec.Template, flattenHelpers) if err != nil { diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index 0645e8ba3..d4c2ca767 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -24,8 +24,9 @@ import ( ) type ResolverTools struct { - Context context.Context - K8sClient client.Client + InstanceNamespace string + Context context.Context + K8sClient client.Client } // TODO: temp workaround for panic in devfile/api when using plugin overrides. See: https://github.com/devfile/api/issues/296 @@ -118,6 +119,10 @@ func resolvePluginComponent( switch { // TODO: Add support for plugin ID and URI case plugin.Kubernetes != nil: + // Search in devworkspace's namespace if namespace ref is unset + if plugin.Kubernetes.Namespace == "" { + plugin.Kubernetes.Namespace = tooling.InstanceNamespace + } resolvedPlugin, pluginMeta, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) case plugin.Uri != "": err = fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) @@ -155,6 +160,7 @@ func resolvePluginComponentByKubernetesReference( name string, plugin *devworkspace.PluginComponent, tooling ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, pluginLabels map[string]string, err error) { + var dwTemplate devworkspace.DevWorkspaceTemplate namespacedName := types.NamespacedName{ Name: plugin.Kubernetes.Name, From 037b9826d8b30b4206e6803d2cb7ce9118a05a25 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 14 Jan 2021 17:53:24 -0500 Subject: [PATCH 12/16] Make DevWorkspace controller own DevWorkspaceTemplates Configure the DevWorkspace controller as an owner of DevWorkspaceTemplates, in order to trigger reconciles when DevWorkspaceTemplates owned by DevWorkspaces are updated. Signed-off-by: Angel Misevski --- controllers/workspace/devworkspace_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 2153e04e2..94c911ef7 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -397,6 +397,10 @@ func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error { // TODO: Set up indexing https://book.kubebuilder.io/cronjob-tutorial/controller-implementation.html#setup return ctrl.NewControllerManagedBy(mgr). For(&devworkspace.DevWorkspace{}). + // List DevWorkspaceTemplates as owned to enable updating workspaces when templates + // are changed; this should be moved to whichever controller is responsible for flattening + // DevWorkspaces + Owns(&devworkspace.DevWorkspaceTemplate{}). Owns(&appsv1.Deployment{}). Owns(&batchv1.Job{}). Owns(&controllerv1alpha1.Component{}). From c2ae3edfe5d14fdd0b05616b59efb3d20fd15767 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 15 Jan 2021 15:04:40 -0500 Subject: [PATCH 13/16] Add DevWorkspaceTemplates to aggregate view and edit roles Signed-off-by: Angel Misevski --- config/components/rbac/edit-workspaces-cluster-role.yaml | 1 + config/components/rbac/view-workspaces-cluster-role.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/config/components/rbac/edit-workspaces-cluster-role.yaml b/config/components/rbac/edit-workspaces-cluster-role.yaml index cecf70567..52ce7e20a 100644 --- a/config/components/rbac/edit-workspaces-cluster-role.yaml +++ b/config/components/rbac/edit-workspaces-cluster-role.yaml @@ -12,6 +12,7 @@ rules: - workspace.devfile.io resources: - devworkspaces + - devworkspacetemplates verbs: - create - delete diff --git a/config/components/rbac/view-workspaces-cluster-role.yaml b/config/components/rbac/view-workspaces-cluster-role.yaml index 09c5cf05f..a89cadd4a 100644 --- a/config/components/rbac/view-workspaces-cluster-role.yaml +++ b/config/components/rbac/view-workspaces-cluster-role.yaml @@ -13,6 +13,7 @@ rules: - workspace.devfile.io resources: - devworkspaces + - devworkspacetemplates verbs: - get - list From 4eb99ea3635608666bf8f0f4a6a9a317afbdde4d Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 21 Jan 2021 17:17:49 -0500 Subject: [PATCH 14/16] Remove proof-of-concept for plugin/editor compatibility Feature needs more time in the oven -- especially around how annotations/metadata is used, or whether this is something that should be implemented in the controller at all. Signed-off-by: Angel Misevski --- pkg/library/flatten/compatibility.go | 83 ------------------- pkg/library/flatten/flatten.go | 4 - samples/plugins/machine-exec.yaml | 2 - samples/plugins/theia-next.yaml | 2 - samples/plugins/vscode-typescript.yaml | 2 - samples/plugins/web-terminal-dev.yaml | 2 - samples/plugins/web-terminal.yaml | 2 - .../with-k8s-ref/incompatible-plugins.yaml | 13 --- 8 files changed, 110 deletions(-) delete mode 100644 pkg/library/flatten/compatibility.go delete mode 100644 samples/with-k8s-ref/incompatible-plugins.yaml diff --git a/pkg/library/flatten/compatibility.go b/pkg/library/flatten/compatibility.go deleted file mode 100644 index 5b8ecf278..000000000 --- a/pkg/library/flatten/compatibility.go +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2019-2020 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 flatten - -import ( - "fmt" - "strings" -) - -const ( - EditorNameLabel = "devworkspace.devfile.io/editor-name" - EditorCompatibilityLabel = "devworkspace.devfile.io/editor-compatibility" -) - -func checkPluginsCompatibility(resolveCtx *resolutionContextTree) error { - editorNames, pluginReqEditors := processSubtreeCompatibility(resolveCtx) - if len(editorNames) == 0 && len(pluginReqEditors) > 0 { - var message []string - for pluginEditor, pluginComponents := range pluginReqEditors { - message = append(message, - fmt.Sprintf("Component(s) [%s] depend on editor %s", strings.Join(pluginComponents, ", "), pluginEditor)) - } - return fmt.Errorf("invalid plugins defined in devworkspace: no editor defined in workspace but %s", strings.Join(message, ". ")) - } - if len(editorNames) > 1 { - var editors []string - for editorName, editorComponent := range editorNames { - editors = append(editors, fmt.Sprintf("Component %s defines editor %s", editorComponent[0], editorName)) - } - return fmt.Errorf("devworkspace defines multiple editors: %s", strings.Join(editors, ", ")) - } - var editorName, editorComponentName string - for name, componentNames := range editorNames { - editorName = name - if len(componentNames) > 1 { - return fmt.Errorf("multiple components define the same editor: [%s]", strings.Join(componentNames, ", ")) - } - editorComponentName = componentNames[0] - } - for pluginReqEditor, pluginComponents := range pluginReqEditors { - if pluginReqEditor != editorName { - return fmt.Errorf("devworkspace uses editor %s (defined in component %s) but plugins [%s] depend on editor %s", - editorName, editorComponentName, strings.Join(pluginComponents, ", "), pluginReqEditor) - } - } - - return nil -} - -func processSubtreeCompatibility(resolveCtx *resolutionContextTree) (editors, pluginReqEditors map[string][]string) { - editors = map[string][]string{} - pluginReqEditors = map[string][]string{} - if resolveCtx.pluginMetadata != nil { - if editor := resolveCtx.pluginMetadata[EditorNameLabel]; editor != "" { - editors[editor] = append(editors[editor], resolveCtx.componentName) - } - if editorCompat := resolveCtx.pluginMetadata[EditorCompatibilityLabel]; editorCompat != "" { - pluginReqEditors[editorCompat] = append(pluginReqEditors[editorCompat], resolveCtx.componentName) - } - } - for _, plugin := range resolveCtx.plugins { - subEditors, subPluginReqEditors := processSubtreeCompatibility(plugin) - mergeMaps(subEditors, editors) - mergeMaps(subPluginReqEditors, pluginReqEditors) - } - return editors, pluginReqEditors -} - -func mergeMaps(from, into map[string][]string) { - for k, v := range from { - into[k] = append(into[k], v...) - } -} diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index d4c2ca767..328677b86 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -54,10 +54,6 @@ func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, toolin if err != nil { return nil, err } - err = checkPluginsCompatibility(resolutionCtx) - if err != nil { - return nil, err - } return resolvedDW, nil } diff --git a/samples/plugins/machine-exec.yaml b/samples/plugins/machine-exec.yaml index 28064f43d..53ca3b432 100644 --- a/samples/plugins/machine-exec.yaml +++ b/samples/plugins/machine-exec.yaml @@ -2,8 +2,6 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: machine-exec - labels: - "devworkspace.devfile.io/editor-compatibility": "che-theia" spec: components: - name: che-machine-exec diff --git a/samples/plugins/theia-next.yaml b/samples/plugins/theia-next.yaml index 8c3e9a6c7..f9b011fd6 100644 --- a/samples/plugins/theia-next.yaml +++ b/samples/plugins/theia-next.yaml @@ -2,8 +2,6 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: theia-next - labels: - "devworkspace.devfile.io/editor-name": "che-theia" spec: components: - name: plugins diff --git a/samples/plugins/vscode-typescript.yaml b/samples/plugins/vscode-typescript.yaml index f423aba8d..6f9571747 100644 --- a/samples/plugins/vscode-typescript.yaml +++ b/samples/plugins/vscode-typescript.yaml @@ -2,8 +2,6 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: vscode-typescript - labels: - "devworkspace.devfile.io/editor-compatibility": "che-theia" spec: components: - name: sidecar-typescript diff --git a/samples/plugins/web-terminal-dev.yaml b/samples/plugins/web-terminal-dev.yaml index 17cc12ab5..628d316d6 100644 --- a/samples/plugins/web-terminal-dev.yaml +++ b/samples/plugins/web-terminal-dev.yaml @@ -2,8 +2,6 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: web-terminal-dev - labels: - "devworkspace.devfile.io/editor-name": "web-terminal" spec: components: - name: web-terminal diff --git a/samples/plugins/web-terminal.yaml b/samples/plugins/web-terminal.yaml index 01fff49f0..bc9d0500d 100644 --- a/samples/plugins/web-terminal.yaml +++ b/samples/plugins/web-terminal.yaml @@ -2,8 +2,6 @@ kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 metadata: name: web-terminal - labels: - "devworkspace.devfile.io/editor-name": "web-terminal" spec: components: - name: web-terminal diff --git a/samples/with-k8s-ref/incompatible-plugins.yaml b/samples/with-k8s-ref/incompatible-plugins.yaml deleted file mode 100644 index 248ae603d..000000000 --- a/samples/with-k8s-ref/incompatible-plugins.yaml +++ /dev/null @@ -1,13 +0,0 @@ -kind: DevWorkspace -apiVersion: workspace.devfile.io/v1alpha2 -metadata: - name: incompatible-plugins -spec: - started: true - template: - components: - - name: terminal - plugin: - kubernetes: - name: machine-exec - namespace: devworkspace-plugins From e6cf53a1d2df45294e6dc26a3f333987da74e478 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Tue, 26 Jan 2021 12:35:51 -0500 Subject: [PATCH 15/16] Rework plugin samples to separate components better Move remote-runtime-injector and vsx-installer to separate plugins that are imported by theia, rather than having a huge theia plugin definition Signed-off-by: Angel Misevski --- samples/plugins/remote-runtime-injector.yaml | 27 +++++++++++ samples/plugins/theia-next.yaml | 51 ++++---------------- samples/plugins/vsx-installer.yaml | 40 +++++++++++++++ 3 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 samples/plugins/remote-runtime-injector.yaml create mode 100644 samples/plugins/vsx-installer.yaml diff --git a/samples/plugins/remote-runtime-injector.yaml b/samples/plugins/remote-runtime-injector.yaml new file mode 100644 index 000000000..6046c5849 --- /dev/null +++ b/samples/plugins/remote-runtime-injector.yaml @@ -0,0 +1,27 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: remote-runtime-injector +spec: + components: + - name: remote-runtime-injector + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: #### corresponds to `initContainer` definition in old meta.yaml. + image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + commands: + - id: inject-theia-in-remote-sidecar + apply: + component: remote-runtime-injector + events: + preStart: + - inject-theia-in-remote-sidecar diff --git a/samples/plugins/theia-next.yaml b/samples/plugins/theia-next.yaml index f9b011fd6..d56eb09c9 100644 --- a/samples/plugins/theia-next.yaml +++ b/samples/plugins/theia-next.yaml @@ -8,37 +8,16 @@ spec: volume: {} - name: remote-endpoint volume: {} # TODO: Fix this once ephemeral volumes are supported - - name: vsx-installer # Mainly reads the container objects and searches for those - # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls - # Those found in the dedicated containers components are with a sidecar, - # Those found in the che-theia container are without a sidecar. - attributes: - "app.kubernetes.io/part-of": che-theia.eclipse.org - "app.kubernetes.io/component": bootstrapper - container: - args: - - /bin/sh - - '-c' - - > - workspace=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}) && for container in $(echo $workspace | sed -e 's;[[,]\({"attributes":{"app.kubernetes.io\);\n\1;g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do dest=$(echo "$container" | sed 's;.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*;\1;' - ) ; urls=$(echo "$container" | sed 's;.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*;\1;' - ) ; mkdir -p $dest; for url in $(echo $urls | sed 's/[",]/ /g' - ); do echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url); done; done - image: 'quay.io/samsahai/curl:latest' - volumeMounts: - - path: "/plugins" - name: plugins + - name: vsx-installer + plugin: + kubernetes: + name: vsx-installer + namespace: devworkspace-plugins - name: remote-runtime-injector - attributes: - "app.kubernetes.io/part-of": che-theia.eclipse.org - "app.kubernetes.io/component": bootstrapper - container: #### corresponds to `initContainer` definition in old meta.yaml. - image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" - volumeMounts: - - path: "/remote-endpoint" - name: remote-endpoint - env: - - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE - value: /remote-endpoint/plugin-remote-endpoint - - name: REMOTE_ENDPOINT_VOLUME_NAME - value: remote-endpoint + plugin: + kubernetes: + name: remote-runtime-injector + namespace: devworkspace-plugins - name: theia-ide attributes: "app.kubernetes.io/name": che-theia.eclipse.org @@ -101,15 +80,3 @@ spec: exposure: public targetPort: 13133 protocol: http - commands: - # Commands coming from plugin editor - - id: inject-theia-in-remote-sidecar - apply: - component: remote-runtime-injector - - id: copy-vsx - apply: - component: vsx-installer - events: - preStart: - - inject-theia-in-remote-sidecar - - copy-vsx diff --git a/samples/plugins/vsx-installer.yaml b/samples/plugins/vsx-installer.yaml new file mode 100644 index 000000000..b49b21f71 --- /dev/null +++ b/samples/plugins/vsx-installer.yaml @@ -0,0 +1,40 @@ +kind: DevWorkspaceTemplate +apiVersion: workspace.devfile.io/v1alpha2 +metadata: + name: vsx-installer +spec: + components: + - name: vsx-installer # Mainly reads the container objects and searches for those + # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls + # Those found in the dedicated containers components are with a sidecar, + # Those found in the che-theia container are without a sidecar. + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: + image: 'quay.io/samsahai/curl:latest' + volumeMounts: + - path: "/plugins" + name: plugins + args: + - /bin/sh + - '-c' + - | + KUBE_API_ENDPOINT="https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}" &&\ + TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) &&\ + WORKSPACE=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer ${TOKEN}" $KUBE_API_ENDPOINT) &&\ + for container in $(echo $WORKSPACE | sed -e 's;[[,]\({"attributes":{"app.kubernetes.io\);\n\1;g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do \ + dest=$(echo "$container" | sed 's;.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*;\1;' - ) ;\ + urls=$(echo "$container" | sed 's;.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*;\1;' - ) ;\ + mkdir -p $dest ;\ + for url in $(echo $urls | sed 's/[",]/ /g' - ); do \ + echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url) ;\ + done \ + done \ + commands: + - id: copy-vsx + apply: + component: vsx-installer + events: + preStart: + - copy-vsx From 50eac654af8df983563af567809b900164a7d540 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 1 Feb 2021 09:36:39 -0500 Subject: [PATCH 16/16] Move test cases related to compatibility out of test suite Signed-off-by: Angel Misevski --- .../testdata/{ => disabled}/error_duplicate-editors.yaml | 2 +- .../flatten/testdata/{ => disabled}/error_multiple-editors.yaml | 2 +- .../{ => disabled}/error_plugin-needs-missing-editor.yaml | 2 +- .../testdata/{ => disabled}/error_plugins-incompatible.yaml | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename pkg/library/flatten/testdata/{ => disabled}/error_duplicate-editors.yaml (96%) rename pkg/library/flatten/testdata/{ => disabled}/error_multiple-editors.yaml (96%) rename pkg/library/flatten/testdata/{ => disabled}/error_plugin-needs-missing-editor.yaml (94%) rename pkg/library/flatten/testdata/{ => disabled}/error_plugins-incompatible.yaml (100%) diff --git a/pkg/library/flatten/testdata/error_duplicate-editors.yaml b/pkg/library/flatten/testdata/disabled/error_duplicate-editors.yaml similarity index 96% rename from pkg/library/flatten/testdata/error_duplicate-editors.yaml rename to pkg/library/flatten/testdata/disabled/error_duplicate-editors.yaml index e510e86e9..eabc48241 100644 --- a/pkg/library/flatten/testdata/error_duplicate-editors.yaml +++ b/pkg/library/flatten/testdata/disabled/error_duplicate-editors.yaml @@ -1,4 +1,4 @@ -name: "Plugins incompatible with editor" +name: "Duplicate editors" input: workspace: diff --git a/pkg/library/flatten/testdata/error_multiple-editors.yaml b/pkg/library/flatten/testdata/disabled/error_multiple-editors.yaml similarity index 96% rename from pkg/library/flatten/testdata/error_multiple-editors.yaml rename to pkg/library/flatten/testdata/disabled/error_multiple-editors.yaml index 7de46b270..d99b52ea0 100644 --- a/pkg/library/flatten/testdata/error_multiple-editors.yaml +++ b/pkg/library/flatten/testdata/disabled/error_multiple-editors.yaml @@ -1,4 +1,4 @@ -name: "Plugins incompatible with editor" +name: "Multiple editors in one workspace" input: workspace: diff --git a/pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml b/pkg/library/flatten/testdata/disabled/error_plugin-needs-missing-editor.yaml similarity index 94% rename from pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml rename to pkg/library/flatten/testdata/disabled/error_plugin-needs-missing-editor.yaml index ed9500ffa..ee90ac610 100644 --- a/pkg/library/flatten/testdata/error_plugin-needs-missing-editor.yaml +++ b/pkg/library/flatten/testdata/disabled/error_plugin-needs-missing-editor.yaml @@ -1,4 +1,4 @@ -name: "Plugins incompatible with editor" +name: "Plugin needs missing editor" input: workspace: diff --git a/pkg/library/flatten/testdata/error_plugins-incompatible.yaml b/pkg/library/flatten/testdata/disabled/error_plugins-incompatible.yaml similarity index 100% rename from pkg/library/flatten/testdata/error_plugins-incompatible.yaml rename to pkg/library/flatten/testdata/disabled/error_plugins-incompatible.yaml