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/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 diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 4fecdb124..94c911ef7 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" @@ -123,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) } @@ -139,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 @@ -170,10 +173,24 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct 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. + 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{ + InstanceNamespace: workspace.Namespace, + 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") @@ -197,7 +214,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) { @@ -225,7 +242,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 { @@ -239,9 +256,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 } @@ -285,7 +302,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 { @@ -298,17 +315,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 } @@ -351,8 +368,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") @@ -375,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{}). 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"}, }, }, }, 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/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..328677b86 --- /dev/null +++ b/pkg/library/flatten/flatten.go @@ -0,0 +1,173 @@ +// +// 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/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ResolverTools struct { + 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 +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: +// - Implement flattening for DevWorkspace parents +// - Implement plugin references by ID and URI +func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { + resolutionCtx := &resolutionContextTree{} + resolvedDW, err := recursiveResolve(workspace, tooling, 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 + } + 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, pluginMeta, err := resolvePluginComponent(component.Name, component.Plugin, tooling) + if err != nil { + return nil, err + } + 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) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, pluginMeta map[string]string, err error) { + 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) + case plugin.Id != "": + err = fmt.Errorf("failed to resolve plugin %s: only plugins specified by kubernetes reference are supported", name) + default: + err = fmt.Errorf("plugin %s does not define any resources", name) + } + if err != nil { + return nil, nil, err + } + + if plugin.Components != nil || plugin.Commands != nil { + // 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, nil, err + } + resolvedPlugin.DevWorkspaceTemplateSpecContent = *overrideSpec + } + return resolvedPlugin, pluginMeta, nil +} + +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) + if err != nil { + 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, dwTemplate.Labels, nil +} 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/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/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/disabled/error_duplicate-editors.yaml b/pkg/library/flatten/testdata/disabled/error_duplicate-editors.yaml new file mode 100644 index 000000000..eabc48241 --- /dev/null +++ b/pkg/library/flatten/testdata/disabled/error_duplicate-editors.yaml @@ -0,0 +1,41 @@ +name: "Duplicate editors" + +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/disabled/error_multiple-editors.yaml b/pkg/library/flatten/testdata/disabled/error_multiple-editors.yaml new file mode 100644 index 000000000..d99b52ea0 --- /dev/null +++ b/pkg/library/flatten/testdata/disabled/error_multiple-editors.yaml @@ -0,0 +1,41 @@ +name: "Multiple editors in one workspace" + +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/disabled/error_plugin-needs-missing-editor.yaml b/pkg/library/flatten/testdata/disabled/error_plugin-needs-missing-editor.yaml new file mode 100644 index 000000000..ee90ac610 --- /dev/null +++ b/pkg/library/flatten/testdata/disabled/error_plugin-needs-missing-editor.yaml @@ -0,0 +1,26 @@ +name: "Plugin needs missing 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/disabled/error_plugins-incompatible.yaml b/pkg/library/flatten/testdata/disabled/error_plugins-incompatible.yaml new file mode 100644 index 000000000..91942950b --- /dev/null +++ b/pkg/library/flatten/testdata/disabled/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/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_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_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/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" 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 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/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 new file mode 100644 index 000000000..d56eb09c9 --- /dev/null +++ b/samples/plugins/theia-next.yaml @@ -0,0 +1,82 @@ +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 + plugin: + kubernetes: + name: vsx-installer + namespace: devworkspace-plugins + - name: remote-runtime-injector + plugin: + kubernetes: + name: remote-runtime-injector + namespace: devworkspace-plugins + - 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 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/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 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