├── .ci ├── common.sh ├── oci-devworkspace-happy-path.sh ├── oci.Dockerfile └── openshift_e2e.sh ├── .clomonitor.yaml ├── .devfile.yaml ├── .dockerignore ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── other.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── code-coverage.yml │ ├── devtools-image-build.yml │ ├── next-build.yml │ ├── pr.yml │ ├── release.yml │ └── scorecard.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── OWNERS ├── PROJECT ├── README.md ├── apis └── controller │ └── v1alpha1 │ ├── attributes.go │ ├── common.go │ ├── devfile.go │ ├── devworkspaceoperatorconfig_types.go │ ├── devworkspacerouting_types.go │ ├── doc.go │ ├── errors.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── build ├── Dockerfile ├── bin │ ├── entrypoint │ └── user_setup ├── buildkitd.toml ├── bundle.Dockerfile ├── devtools.Dockerfile ├── index.next-digest.Dockerfile ├── index.next.Dockerfile ├── index.release-digest.Dockerfile ├── index.release.Dockerfile ├── make │ ├── deploy.mk │ ├── olm.mk │ └── version.mk └── scripts │ ├── build_digests_bundle.sh │ ├── build_index_image.sh │ └── generate_deployment.sh ├── catalog-source.yaml ├── codecov.yml ├── controllers ├── cleanupcronjob │ ├── cleanupcronjob_controller.go │ ├── cleanupcronjob_controller_test.go │ └── suite_test.go ├── controller │ └── devworkspacerouting │ │ ├── conversion │ │ └── conversion.go │ │ ├── devworkspacerouting_controller.go │ │ ├── devworkspacerouting_controller_test.go │ │ ├── predicates.go │ │ ├── solvers │ │ ├── basic_solver.go │ │ ├── cluster_solver.go │ │ ├── common.go │ │ ├── errors.go │ │ ├── resolve_endpoints.go │ │ ├── resolve_endpoints_test.go │ │ └── solver.go │ │ ├── suite_test.go │ │ ├── sync_ingresses.go │ │ ├── sync_routes.go │ │ ├── sync_services.go │ │ ├── testdata │ │ └── route.crd.yaml │ │ └── util_test.go └── workspace │ ├── cleanup.go │ ├── condition.go │ ├── devworkspace_controller.go │ ├── devworkspace_controller_test.go │ ├── eventhandlers.go │ ├── finalize.go │ ├── http.go │ ├── http_test.go │ ├── internal │ └── testutil │ │ └── http.go │ ├── metrics │ ├── deployment_provisioning.go │ ├── failure_reason.go │ ├── metrics.go │ └── update.go │ ├── predicates.go │ ├── status.go │ ├── suite_test.go │ ├── testdata │ ├── common-pvc-test-devworkspace.yaml │ ├── test-devworkspace-duplicate-ports.yaml │ └── test-devworkspace.yaml │ ├── util_test.go │ └── validation.go ├── deploy ├── bundle │ ├── manifests │ │ ├── controller.devfile.io_devworkspaceoperatorconfigs.yaml │ │ ├── controller.devfile.io_devworkspaceroutings.yaml │ │ ├── devworkspace-controller-edit-workspaces_rbac.authorization.k8s.io_v1_clusterrole.yaml │ │ ├── devworkspace-controller-manager-service_v1_service.yaml │ │ ├── devworkspace-controller-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml │ │ ├── devworkspace-controller-metrics_v1_service.yaml │ │ ├── devworkspace-controller-view-workspaces_rbac.authorization.k8s.io_v1_clusterrole.yaml │ │ ├── devworkspace-operator.clusterserviceversion.yaml │ │ ├── workspace.devfile.io_devworkspaces.yaml │ │ └── workspace.devfile.io_devworkspacetemplates.yaml │ ├── metadata │ │ └── annotations.yaml │ └── tests │ │ └── scorecard │ │ └── config.yaml ├── default-config.yaml ├── deployment │ ├── kubernetes │ │ ├── combined.yaml │ │ └── objects │ │ │ ├── devworkspace-controller-edit-workspaces.ClusterRole.yaml │ │ │ ├── devworkspace-controller-leader-election-role.Role.yaml │ │ │ ├── devworkspace-controller-leader-election-rolebinding.RoleBinding.yaml │ │ │ ├── devworkspace-controller-manager-service.Service.yaml │ │ │ ├── devworkspace-controller-manager.Deployment.yaml │ │ │ ├── devworkspace-controller-metrics-reader.ClusterRole.yaml │ │ │ ├── devworkspace-controller-metrics.Service.yaml │ │ │ ├── devworkspace-controller-proxy-role.ClusterRole.yaml │ │ │ ├── devworkspace-controller-proxy-rolebinding.ClusterRoleBinding.yaml │ │ │ ├── devworkspace-controller-role.ClusterRole.yaml │ │ │ ├── devworkspace-controller-rolebinding.ClusterRoleBinding.yaml │ │ │ ├── devworkspace-controller-selfsigned-issuer.Issuer.yaml │ │ │ ├── devworkspace-controller-serviceaccount.ServiceAccount.yaml │ │ │ ├── devworkspace-controller-serving-cert.Certificate.yaml │ │ │ ├── devworkspace-controller-view-workspaces.ClusterRole.yaml │ │ │ ├── devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml │ │ │ ├── devworkspaceroutings.controller.devfile.io.CustomResourceDefinition.yaml │ │ │ ├── devworkspaces.workspace.devfile.io.CustomResourceDefinition.yaml │ │ │ └── devworkspacetemplates.workspace.devfile.io.CustomResourceDefinition.yaml │ └── openshift │ │ ├── combined.yaml │ │ └── objects │ │ ├── devworkspace-controller-edit-workspaces.ClusterRole.yaml │ │ ├── devworkspace-controller-leader-election-role.Role.yaml │ │ ├── devworkspace-controller-leader-election-rolebinding.RoleBinding.yaml │ │ ├── devworkspace-controller-manager-service.Service.yaml │ │ ├── devworkspace-controller-manager.Deployment.yaml │ │ ├── devworkspace-controller-metrics-reader.ClusterRole.yaml │ │ ├── devworkspace-controller-metrics.Service.yaml │ │ ├── devworkspace-controller-proxy-role.ClusterRole.yaml │ │ ├── devworkspace-controller-proxy-rolebinding.ClusterRoleBinding.yaml │ │ ├── devworkspace-controller-role.ClusterRole.yaml │ │ ├── devworkspace-controller-rolebinding.ClusterRoleBinding.yaml │ │ ├── devworkspace-controller-serviceaccount.ServiceAccount.yaml │ │ ├── devworkspace-controller-view-workspaces.ClusterRole.yaml │ │ ├── devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml │ │ ├── devworkspaceroutings.controller.devfile.io.CustomResourceDefinition.yaml │ │ ├── devworkspaces.workspace.devfile.io.CustomResourceDefinition.yaml │ │ └── devworkspacetemplates.workspace.devfile.io.CustomResourceDefinition.yaml └── templates │ ├── base │ ├── kustomization.yaml │ ├── manager_image_patch.yaml │ └── webhooks_name_env_patch.yaml │ ├── cert-manager │ ├── crd_webhooks_patch.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── manager_certmanager_patch.yaml │ ├── components │ ├── cert-manager │ │ ├── kustomization.yaml │ │ ├── kustomizeconfig.yaml │ │ └── self-signed-certificates.yaml │ ├── csv │ │ ├── clusterserviceversion.yaml │ │ └── kustomization.yaml │ ├── manager │ │ ├── kustomization.yaml │ │ ├── kustomizeconfig.yaml │ │ ├── manager.yaml │ │ ├── service-metrics.yaml │ │ ├── service.yaml │ │ └── serviceaccount.yaml │ └── rbac │ │ ├── auth_proxy_client_cluster_role.yaml │ │ ├── auth_proxy_cluster_role.yaml │ │ ├── auth_proxy_cluster_role_binding.yaml │ │ ├── edit_workspaces_cluster_role.yaml │ │ ├── kustomization.yaml │ │ ├── kustomizeconfig.yaml │ │ ├── leader_election_role.yaml │ │ ├── leader_election_role_binding.yaml │ │ ├── role.yaml │ │ ├── role_binding.yaml │ │ └── view_workspaces_cluster_role.yaml │ ├── crd │ ├── bases │ │ ├── controller.devfile.io_devworkspaceoperatorconfigs.yaml │ │ └── controller.devfile.io_devworkspaceroutings.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml │ ├── kustomize.pu │ ├── olm │ ├── crd_webhooks_patch.yaml │ ├── kustomization.yaml │ └── prefixed │ │ └── kustomization.yaml │ └── service-ca │ ├── crd_webhooks_patch.yaml │ ├── kustomization.yaml │ ├── manager_service_ca_patch.yaml │ └── service_cert_patch.yaml ├── docs ├── additional-configuration.adoc ├── dwo-configuration.md ├── grafana │ ├── README.md │ ├── grafana-dashboard.json │ └── openshift-console-dashboard.json ├── installation │ ├── kind-without-olm-linux.md │ ├── kind-without-olm-macos.md │ ├── minikube-without-olm.md │ ├── openshift-with-olm.md │ └── openshift-without-olm.md ├── release │ ├── README.md │ ├── prerelease.png │ └── release.png ├── uninstall.md ├── unsupported-devfile-api.adoc └── workspace-capabilities.md ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── img ├── apply-demo.gif ├── devworkspace.png ├── dwo-on-developer-sandbox.png ├── get-demo.gif ├── intellij.png ├── logo.png ├── vscode.png └── wto-on-developer-sandbox.png ├── internal ├── images │ └── image.go └── map │ └── map.go ├── license_header.txt ├── main.go ├── make-release.sh ├── olm-catalog ├── next-digest │ ├── channel.yaml │ └── package.yaml ├── next │ ├── channel.yaml │ └── package.yaml ├── release-digest │ ├── channel.yaml │ ├── devworkspace-operator.v0.21.1.bundle.yaml │ ├── devworkspace-operator.v0.21.2.bundle.yaml │ ├── devworkspace-operator.v0.22.0.bundle.yaml │ ├── devworkspace-operator.v0.23.0.bundle.yaml │ ├── devworkspace-operator.v0.24.0.bundle.yaml │ ├── devworkspace-operator.v0.25.0.bundle.yaml │ ├── devworkspace-operator.v0.26.0.bundle.yaml │ ├── devworkspace-operator.v0.27.0.bundle.yaml │ ├── devworkspace-operator.v0.28.0.bundle.yaml │ ├── devworkspace-operator.v0.29.0.bundle.yaml │ ├── devworkspace-operator.v0.30.0.bundle.yaml │ ├── devworkspace-operator.v0.31.0.bundle.yaml │ ├── devworkspace-operator.v0.31.1.bundle.yaml │ ├── devworkspace-operator.v0.31.2.bundle.yaml │ ├── devworkspace-operator.v0.32.0.bundle.yaml │ ├── devworkspace-operator.v0.32.1.bundle.yaml │ ├── devworkspace-operator.v0.33.0.bundle.yaml │ ├── devworkspace-operator.v0.34.0.bundle.yaml │ └── package.yaml └── release │ ├── channel.yaml │ ├── devworkspace-operator.v0.10.0.bundle.yaml │ ├── devworkspace-operator.v0.11.0.bundle.yaml │ ├── devworkspace-operator.v0.12.0.bundle.yaml │ ├── devworkspace-operator.v0.12.1.bundle.yaml │ ├── devworkspace-operator.v0.12.2.bundle.yaml │ ├── devworkspace-operator.v0.12.3.bundle.yaml │ ├── devworkspace-operator.v0.13.0.bundle.yaml │ ├── devworkspace-operator.v0.14.0.bundle.yaml │ ├── devworkspace-operator.v0.14.1.bundle.yaml │ ├── devworkspace-operator.v0.15.0.bundle.yaml │ ├── devworkspace-operator.v0.15.1.bundle.yaml │ ├── devworkspace-operator.v0.15.2.bundle.yaml │ ├── devworkspace-operator.v0.15.3.bundle.yaml │ ├── devworkspace-operator.v0.16.0.bundle.yaml │ ├── devworkspace-operator.v0.17.0.bundle.yaml │ ├── devworkspace-operator.v0.18.0.bundle.yaml │ ├── devworkspace-operator.v0.18.1.bundle.yaml │ ├── devworkspace-operator.v0.19.0.bundle.yaml │ ├── devworkspace-operator.v0.19.1.bundle.yaml │ ├── devworkspace-operator.v0.20.0.bundle.yaml │ ├── devworkspace-operator.v0.21.0.bundle.yaml │ ├── devworkspace-operator.v0.21.1.bundle.yaml │ ├── devworkspace-operator.v0.21.2.bundle.yaml │ ├── devworkspace-operator.v0.22.0.bundle.yaml │ ├── devworkspace-operator.v0.23.0.bundle.yaml │ ├── devworkspace-operator.v0.24.0.bundle.yaml │ ├── devworkspace-operator.v0.25.0.bundle.yaml │ ├── devworkspace-operator.v0.26.0.bundle.yaml │ ├── devworkspace-operator.v0.27.0.bundle.yaml │ ├── devworkspace-operator.v0.28.0.bundle.yaml │ ├── devworkspace-operator.v0.29.0.bundle.yaml │ ├── devworkspace-operator.v0.30.0.bundle.yaml │ ├── devworkspace-operator.v0.31.0.bundle.yaml │ ├── devworkspace-operator.v0.31.1.bundle.yaml │ ├── devworkspace-operator.v0.31.2.bundle.yaml │ ├── devworkspace-operator.v0.32.0.bundle.yaml │ ├── devworkspace-operator.v0.32.1.bundle.yaml │ ├── devworkspace-operator.v0.33.0.bundle.yaml │ ├── devworkspace-operator.v0.34.0.bundle.yaml │ └── package.yaml ├── patch └── patch_crds.sh ├── pkg ├── cache │ └── cache.go ├── common │ ├── naming.go │ └── types.go ├── conditions │ └── conditions.go ├── config │ ├── common_test.go │ ├── configmap │ │ ├── config.go │ │ ├── doc.go │ │ └── property.go │ ├── defaults.go │ ├── env.go │ ├── migrate.go │ ├── migrate_test.go │ ├── predicates.go │ ├── proxy │ │ ├── openshift.go │ │ └── openshift_test.go │ ├── sync.go │ └── sync_test.go ├── constants │ ├── attributes.go │ ├── constants.go │ ├── env.go │ ├── finalizers.go │ └── metadata.go ├── dwerrors │ └── errors.go ├── infrastructure │ ├── cluster.go │ ├── namespace.go │ └── webhook.go ├── library │ ├── annotate │ │ ├── annotations.go │ │ ├── plugins.go │ │ └── urls.go │ ├── constants │ │ └── constants.go │ ├── container │ │ ├── container.go │ │ ├── container_test.go │ │ ├── conversion.go │ │ ├── mountSources.go │ │ └── testdata │ │ │ ├── component │ │ │ ├── converts-all-fields.yaml │ │ │ └── ignores-non-container-components.yaml │ │ │ ├── container │ │ │ ├── endpoints-common-port.yaml │ │ │ ├── endpoints-uses-name-if-shorter-than-15-chars.yaml │ │ │ ├── init-container-distinct-container-by-events.yaml.yaml │ │ │ ├── resources-error-cpu-limit-less-than-request.yaml │ │ │ ├── resources-error-invalid-memorylimit.yaml │ │ │ ├── resources-error-memory-limit-less-than-requst.yaml │ │ │ ├── resources-fixes-memory-limit-less-than-default-request.yaml │ │ │ └── resources-handles-cpu-and-memory.yaml │ │ │ ├── mountsources │ │ │ ├── projects-source-and-root-first-env-vars.yaml.yaml │ │ │ ├── projects-source-is-set-correctly-from-clonepath.yaml │ │ │ └── projects-source-is-set-correctly-from-project-name.yaml │ │ │ ├── parent │ │ │ └── error-has-parent.yaml │ │ │ ├── plugin │ │ │ └── error-has-plugins.yaml │ │ │ └── volume │ │ │ ├── container-that-mounts-projects-directly.yaml │ │ │ └── mountSources.yaml │ ├── defaults │ │ └── helper.go │ ├── env │ │ ├── testdata │ │ │ └── workspace-env │ │ │ │ ├── adds-workspaceEnv-successfully-when-duplicate.yaml │ │ │ │ ├── adds-workspaceEnv-to-all-containers.yaml │ │ │ │ ├── adds-workspaceEnv-written-as-json.yaml │ │ │ │ ├── error_duplicated-workspaceEnv-with-different-value.yaml │ │ │ │ └── error_workspaceEnv-formatted-wrong.yaml │ │ ├── workspaceenv.go │ │ └── workspaceenv_test.go │ ├── flatten │ │ ├── common.go │ │ ├── flatten.go │ │ ├── flatten_test.go │ │ ├── helper.go │ │ ├── internal │ │ │ └── testutil │ │ │ │ ├── common.go │ │ │ │ ├── http.go │ │ │ │ └── k8sClient.go │ │ ├── merge.go │ │ ├── network │ │ │ ├── devfile.go │ │ │ └── fetch.go │ │ └── testdata │ │ │ ├── annotate │ │ │ └── annotate-devfile-with-importing-source.yaml │ │ │ ├── container-contributions │ │ │ ├── adds-resources.yaml │ │ │ ├── adds-unmerged-elements.yaml │ │ │ ├── che-code-usecase.yaml │ │ │ ├── error_multiple-contribution-targets.yaml │ │ │ ├── generic-ide-merge-usecase.yaml │ │ │ ├── merges-list-elements.yaml │ │ │ ├── no-op-if-explicit-opt-out.yaml │ │ │ ├── no-op-if-no-contribution.yaml │ │ │ └── only-contributes-to-defined-resources.yaml │ │ │ ├── disabled │ │ │ ├── error_duplicate-editors.yaml │ │ │ ├── error_multiple-editors.yaml │ │ │ ├── error_plugin-needs-missing-editor.yaml │ │ │ └── error_plugins-incompatible.yaml │ │ │ ├── general │ │ │ ├── fail-nicely-when-no-http-client-provided_id.yaml │ │ │ ├── fail-nicely-when-no-http-client-provided_uri.yaml │ │ │ ├── fail-nicely-when-no-k8s-client-provided.yaml │ │ │ ├── fail-nicely-when-no-namespace.yaml │ │ │ └── fail-nicely-when-no-registry-url.yaml │ │ │ ├── implicit-container-contributions │ │ │ ├── adds-resources.yaml │ │ │ ├── adds-unmerged-elements.yaml │ │ │ ├── che-code-usecase.yaml │ │ │ ├── contributes-to-parent.yaml │ │ │ ├── generic-ide-merge-usecase.yaml │ │ │ ├── merges-list-elements.yaml │ │ │ ├── no-op-if-no-contribution.yaml │ │ │ ├── only-contributes-to-defined-resources.yaml │ │ │ ├── opt-in-non-first-component.yaml │ │ │ ├── opt-out-but-merge-other-component.yaml │ │ │ └── prioritizes-parent-components-for-target.yaml │ │ │ ├── implicit-spec-contributions │ │ │ ├── adds-resources.yaml │ │ │ ├── adds-unmerged-elements.yaml │ │ │ ├── che-code-usecase.yaml │ │ │ ├── contributes-to-parent.yaml │ │ │ ├── error_merged-component-has-apply-command.yml │ │ │ ├── generic-ide-merge-usecase.yaml │ │ │ ├── no-op-if-no-contribution.yaml │ │ │ ├── only-contributes-to-defined-resources.yaml │ │ │ ├── opt-out-but-merge-other-component.yaml │ │ │ ├── prioritizes-parent-components-for-target.yaml │ │ │ ├── updates-commands-for-merged-components.yaml │ │ │ └── updates-commands-for-only-merged-components.yaml │ │ │ ├── k8s-ref │ │ │ ├── already-flattened.yaml │ │ │ ├── error_bad-plugin-merge.yaml │ │ │ ├── error_conflicting-merge.yaml │ │ │ ├── error_error-when-retrieving-plugin.yaml │ │ │ ├── error_plugin-not-found.yaml │ │ │ ├── error_plugin-references-self.yml │ │ │ ├── error_plugins-have-cycle.yml │ │ │ ├── nested-plugins-annotation.yaml │ │ │ ├── nodejs-workspace.yaml │ │ │ └── web-terminal-with-plugin.yaml │ │ │ ├── namespace-restriction │ │ │ ├── error_read-dwt-from-non-approved-namespace.yaml │ │ │ ├── error_read-plain-dwt-from-another-namespace copy.yaml │ │ │ ├── error_read-plain-dwt-from-another-namespace.yaml │ │ │ ├── read-dwt-in-permitted-namespace.yaml.yaml │ │ │ ├── read-dwt-in-same-namespace.yaml │ │ │ └── read-dwt-with-any-namespace-annotation.yaml │ │ │ ├── parent │ │ │ ├── error_parent-has-parent.yaml │ │ │ ├── error_parent-has-plugins.yaml │ │ │ ├── resolve-parent-and-plugins.yaml │ │ │ ├── resolve-parent-by-id.yaml │ │ │ ├── resolve-parent-by-k8s-reference.yaml │ │ │ └── resolve-parent-by-uri.yaml │ │ │ ├── plugin-id │ │ │ ├── error_fetch-unparseable-file.yaml │ │ │ ├── error_invalid-schema-version.yaml │ │ │ ├── error_on-fetch.yaml │ │ │ ├── error_plugin-not-found.yaml │ │ │ ├── error_unparseable-url.yaml │ │ │ ├── resolve-devworkspace-instead-of-devfile.yaml │ │ │ ├── resolve-plugin-by-id.yaml │ │ │ └── resolve-plugin-multiple-registries.yaml │ │ │ ├── plugin-uri │ │ │ ├── error_fetch-unparseable-file.yaml │ │ │ ├── error_invalid-schema-version.yaml │ │ │ ├── error_on-fetch.yaml │ │ │ ├── error_plugin-not-found.yaml │ │ │ ├── resolve-devworkspace-instead-of-devfile.yaml │ │ │ ├── resolve-multiple-plugins-by-uri.yaml │ │ │ └── resolve-plugin-by-uri.yaml │ │ │ ├── spec-contributions │ │ │ ├── adds-resources.yaml │ │ │ ├── adds-unmerged-elements.yaml │ │ │ ├── che-code-usecase.yaml │ │ │ ├── does-not-update-commands-if-not-merged.yaml │ │ │ ├── empty-workspace.yaml │ │ │ ├── error_bad-plugin-merge.yaml │ │ │ ├── error_conflicting-merge.yaml │ │ │ ├── error_error-when-retrieving-plugin.yaml │ │ │ ├── error_fetch-unparseable-file.yaml │ │ │ ├── error_invalid-schema-version.yaml │ │ │ ├── error_merged-component-has-apply-command.yml │ │ │ ├── error_multiple-contribution-targets.yaml │ │ │ ├── error_on-fetch.yaml │ │ │ ├── error_plugin-not-found-k8s.yaml │ │ │ ├── error_plugin-not-found-uri.yaml │ │ │ ├── error_plugin-references-self.yml │ │ │ ├── error_plugins-have-cycle.yml │ │ │ ├── generic-ide-merge-usecase.yaml │ │ │ ├── merges-list-elements.yaml │ │ │ ├── nested-plugins-annotation.yaml │ │ │ ├── no-op-if-explicit-opt-out.yaml │ │ │ ├── no-op-if-no-contribution.yaml │ │ │ ├── nodejs-workspace.yaml │ │ │ ├── only-contributes-to-defined-resources.yaml │ │ │ ├── resolve-devworkspace-instead-of-devfile.yaml │ │ │ ├── resolve-multiple-plugins-by-uri.yaml │ │ │ ├── resolve-plugin-by-uri.yaml │ │ │ ├── updates-commands-for-merged-components.yaml │ │ │ ├── updates-commands-for-only-merged-components.yaml │ │ │ └── web-terminal-with-plugin.yaml │ │ │ └── volume_merging │ │ │ ├── does-nothing-when-no-merge-needed.yaml │ │ │ ├── error-invalid-size-in-merged-volume.yaml │ │ │ ├── keeps-non-volume-components.yaml │ │ │ ├── makes-merged-volume-persistent-if-needed.yaml │ │ │ ├── makes-merged-volume-use-largest-size.yaml │ │ │ └── merges-volumes-from-parent-and-plugins.yaml │ ├── home │ │ ├── persistentHome.go │ │ ├── persistentHome_test.go │ │ └── testdata │ │ │ └── persistent-home │ │ │ ├── creates-home-vm-when-enabled.yaml │ │ │ ├── creates-initcontainer-from-first-component.yaml │ │ │ ├── creates-initcontainer-when-enabled.yaml │ │ │ ├── no-home-vm-when-disabled.yaml │ │ │ ├── noop-if-home-vm-exists.yaml │ │ │ ├── noop-if-home-vm-name-used.yaml │ │ │ ├── noop-if-init-command-already-defined.yaml │ │ │ ├── noop-if-init-component-already-defined.yaml │ │ │ ├── noop-if-init-prestartevent-already-defined.yaml │ │ │ └── noop-if-no-components.yaml │ ├── kubernetes │ │ ├── common_test.go │ │ ├── deserialize.go │ │ ├── deserialize_test.go │ │ ├── provision.go │ │ ├── provision_test.go │ │ ├── testdata │ │ │ ├── k8s_objects │ │ │ │ ├── configmap.yaml │ │ │ │ ├── kubernetes-list.yaml │ │ │ │ ├── non-k8s-object.yaml │ │ │ │ ├── pod.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── unrecognized-kind.yaml │ │ │ └── provision_tests │ │ │ │ ├── creates-k8s-objects.yaml │ │ │ │ ├── error-if-object-exists-no-devworkspaceID.yaml │ │ │ │ ├── error-if-object-exists-no-ownerref.yaml │ │ │ │ ├── error-if-object-exists.yaml │ │ │ │ ├── error-if-object-not-inlined.yaml │ │ │ │ ├── ignores-non-deployByDefault.yaml │ │ │ │ ├── no-k8s-components-devworkspace.yaml │ │ │ │ └── updates-existing-owned-objects.yaml │ │ └── util.go │ ├── lifecycle │ │ ├── command.go │ │ ├── common.go │ │ ├── poststart.go │ │ ├── poststart_test.go │ │ ├── prestart.go │ │ ├── prestart_test.go │ │ ├── prestop.go │ │ ├── prestop_test.go │ │ ├── testdata │ │ │ ├── postStart │ │ │ │ ├── adds_all_postStart_commands.yaml │ │ │ │ ├── basic_postStart.yaml │ │ │ │ ├── error_command_has_env.yaml │ │ │ │ ├── error_postStart_cmd_is_not_exec.yaml │ │ │ │ ├── error_postStart_command_does_not_exist.yaml │ │ │ │ ├── error_postStart_command_uses_nonexistent_container.yaml │ │ │ │ ├── multiple_poststart_commands.yaml │ │ │ │ ├── no_events.yaml │ │ │ │ ├── no_postStart.yaml │ │ │ │ └── workingDir_postStart.yaml │ │ │ ├── preStop │ │ │ │ ├── adds_all_preStop_commands.yaml │ │ │ │ ├── basic_preStop.yaml │ │ │ │ ├── error_command_has_env.yaml │ │ │ │ ├── error_preStop_cmd_is_not_exec.yaml │ │ │ │ ├── error_preStop_command_does_not_exist.yaml │ │ │ │ ├── error_preStop_command_uses_nonexistent_container.yaml │ │ │ │ ├── multiple_prestop_commands.yaml │ │ │ │ ├── no_events.yaml │ │ │ │ ├── no_preStop.yaml │ │ │ │ └── workingDir_preStop.yaml │ │ │ └── prestart │ │ │ │ ├── init_and_main_container.yaml │ │ │ │ ├── no_events.yaml │ │ │ │ ├── persistent_home_initcontainer_first_initcontainer.yaml │ │ │ │ ├── persistent_home_initcontainer_only_initcontainer.yaml │ │ │ │ ├── persistent_home_initcontainer_second_initcontainer.yaml │ │ │ │ ├── prestart_apply_command.yaml │ │ │ │ └── prestart_exec_command.yaml │ │ └── util.go │ ├── overrides │ │ ├── containers.go │ │ ├── containers_test.go │ │ ├── pods.go │ │ ├── pods_test.go │ │ └── testdata │ │ │ ├── container-overrides │ │ │ ├── container-cannot-set-restricted-fields.yaml │ │ │ ├── container-defines-overrides-json.yaml │ │ │ ├── container-defines-overrides.yaml │ │ │ ├── container-overridden-resources-merge.yaml │ │ │ ├── error_cannot-parse-override.yaml │ │ │ ├── overrides-can-add-volumemount.yaml │ │ │ ├── overrides-can-define-volumemount.yaml │ │ │ ├── overrides-can-override-securityContext.yaml │ │ │ ├── overrides-can-override-volumemount.yaml │ │ │ ├── overrides-can-use-delete-directive.yaml │ │ │ ├── overrides-can-use-replace-directive.yaml │ │ │ └── overrides-handles-defaulted-probe-fields.yaml │ │ │ └── pod-overrides │ │ │ ├── error_cannot-parse-component-attribute.yaml │ │ │ ├── error_cannot-parse-global-attribute.yaml │ │ │ ├── overrides-can-add-volumes.yaml │ │ │ ├── overrides-can-define-volumes.yaml │ │ │ ├── overrides-can-override-securityContext.yaml │ │ │ ├── overrides-can-override-volumes.yaml │ │ │ ├── overrides-can-use-delete-directive.yaml │ │ │ ├── overrides-can-use-replace-directive.yaml │ │ │ ├── workspace-component-attribute.yaml │ │ │ ├── workspace-defines-attribute-in-non-container-component.yaml │ │ │ ├── workspace-full-example-json.yaml │ │ │ ├── workspace-full-example.yaml │ │ │ ├── workspace-global-attribute-as-json.yaml │ │ │ ├── workspace-global-attribute.yaml │ │ │ ├── workspace-multiple-attributes.yaml │ │ │ └── workspace-multiple-component-attributes-precedence.yaml │ ├── projects │ │ └── clone.go │ ├── resources │ │ ├── helper.go │ │ └── helper_test.go │ ├── ssh │ │ └── event.go │ └── status │ │ └── check.go ├── provision │ ├── automount │ │ ├── common.go │ │ ├── common_persistenthome_test.go │ │ ├── common_test.go │ │ ├── configmap.go │ │ ├── gitconfig.go │ │ ├── gitconfig_test.go │ │ ├── projected.go │ │ ├── projected_test.go │ │ ├── pvcs.go │ │ ├── secret.go │ │ ├── templates.go │ │ └── testdata │ │ │ ├── errorBadAccessMode.yaml │ │ │ ├── testIncludesConfigmapBinaryData.yaml │ │ │ ├── testProvisionsConfigmaps.yaml │ │ │ ├── testProvisionsProjectedVolumes.yaml │ │ │ ├── testProvisionsSecrets.yaml │ │ │ └── testProvisionsWithAccessMode.yaml │ ├── config │ │ └── config.go │ ├── metadata │ │ ├── envvar.go │ │ └── metadata.go │ ├── storage │ │ ├── asyncStorage.go │ │ ├── asyncstorage │ │ │ ├── cleanup.go │ │ │ ├── configmap.go │ │ │ ├── configuration.go │ │ │ ├── constants.go │ │ │ ├── deployment.go │ │ │ ├── errors.go │ │ │ ├── secret.go │ │ │ ├── service.go │ │ │ ├── sidecar.go │ │ │ ├── ssh.go │ │ │ └── volume.go │ │ ├── cleanup.go │ │ ├── commonStorage.go │ │ ├── commonStorage_test.go │ │ ├── doc.go │ │ ├── ephemeralStorage.go │ │ ├── ephemeralStorage_test.go │ │ ├── perWorkspaceStorage.go │ │ ├── perWorkspaceStorage_test.go │ │ ├── provisioner.go │ │ ├── shared.go │ │ └── testdata │ │ │ ├── common-storage │ │ │ ├── can-make-projects-ephemeral.yaml │ │ │ ├── can-set-ephemeral-volume-size.yaml │ │ │ ├── does-nothing-for-no-storage-needed.yaml │ │ │ ├── error-duplicate-volumes.yaml │ │ │ ├── error-undefined-volume-init-container.yaml │ │ │ ├── error-undefined-volume.yaml │ │ │ ├── error-unparseable-ephemeral-size.yaml │ │ │ ├── handles-ephemeral-volumes.yaml │ │ │ ├── handles-projects-volume-ordering.yaml │ │ │ ├── per-user-alias.yaml │ │ │ ├── projects-volume-overriding.yaml │ │ │ └── rewrites-volumes-for-common-pvc-strategy.yaml │ │ │ ├── ephemeral-storage │ │ │ └── supports-ephemeral-storageclass.yaml │ │ │ └── perWorkspace-storage │ │ │ ├── calculates-pvc-size-ignoring-ephemeral-volumes.yaml │ │ │ ├── calculates-pvc-size-when-all-defined.yaml │ │ │ ├── can-make-projects-ephemeral.yaml │ │ │ ├── can-set-ephemeral-volume-size.yaml │ │ │ ├── does-nothing-for-no-storage-needed.yaml │ │ │ ├── error-duplicate-volumes.yaml │ │ │ ├── error-undefined-volume-init-container.yaml │ │ │ ├── error-undefined-volume.yaml │ │ │ ├── error-unparseable-ephemeral-size.yaml │ │ │ ├── handles-ephemeral-volumes.yaml │ │ │ ├── handles-projects-volume-ordering.yaml │ │ │ ├── projects-volume-overriding.yaml │ │ │ ├── rewrites-volumes-for-perworkspace-pvc-strategy.yaml │ │ │ ├── use-default-size-when-calculated-size-smaller-than-default.yaml │ │ │ └── uses-calculated-pvc-size-when-greater-than-default.yaml │ ├── sync │ │ ├── cluster_api.go │ │ ├── diff.go │ │ ├── diffopts.go │ │ ├── sync.go │ │ └── update.go │ └── workspace │ │ ├── deployment.go │ │ ├── pull_secret.go │ │ ├── rbac │ │ ├── common.go │ │ ├── common_test.go │ │ ├── finalize.go │ │ ├── finalize_test.go │ │ ├── migrate.go │ │ ├── migrate_test.go │ │ ├── role.go │ │ ├── role_test.go │ │ ├── rolebinding.go │ │ └── rolebinding_test.go │ │ ├── routing.go │ │ ├── serviceaccount.go │ │ ├── ssh-askpass.sh │ │ ├── ssh.go │ │ ├── tokens.go │ │ └── tokens_test.go └── webhook │ ├── cluster_role_bindings.go │ ├── cluster_roles.go │ ├── create.go │ ├── deployment.go │ ├── info.go │ ├── init_cfg.go │ ├── kubernetes │ └── tls.go │ ├── openshift │ └── tls.go │ ├── service │ ├── log.go │ └── service.go │ └── service_account.go ├── project-clone ├── Dockerfile ├── internal │ ├── bootstrap │ │ ├── bootstrap.go │ │ ├── cluster.go │ │ └── util.go │ ├── devfile.go │ ├── git │ │ ├── operations.go │ │ └── setup.go │ ├── global.go │ ├── shell │ │ └── execute.go │ ├── utils.go │ └── zip │ │ └── setup.go ├── main.go └── test │ ├── project-clone-test-basic.devworkspace.yaml │ ├── project-clone-test.devworkspace.yaml │ ├── sparse-clone-test.devworkspace.yaml │ └── starter-project-test.devworkspace.yaml ├── samples ├── code-latest.yaml ├── container-overrides.yaml ├── empty.yaml ├── ephemeral-storage.yaml ├── git-clone-sample.yaml ├── idea-latest.yaml ├── per-workspace-storage.yaml ├── plain.yaml ├── plugins │ ├── web-terminal-exec.yaml │ └── web-terminal-tooling.yaml ├── pod-overrides.yaml ├── web-terminal-flattened.yaml └── web-terminal.yaml ├── test ├── e2e │ ├── cmd │ │ └── workspaces_test.go │ └── pkg │ │ ├── client │ │ ├── client.go │ │ ├── devws.go │ │ ├── namespace.go │ │ ├── oc.go │ │ ├── pod.go │ │ ├── rbac.go │ │ └── webhooks.go │ │ ├── config │ │ └── config.go │ │ ├── metadata │ │ └── metadata.go │ │ └── tests │ │ └── devworkspaces_tests.go └── resources │ └── restricted-access-devworkspace.yaml ├── update_devworkspace_crds.sh ├── version └── version.go └── webhook ├── main.go ├── server └── server.go └── workspace ├── config.go ├── handler ├── access_control.go ├── attributes.go ├── deployment.go ├── exec.go ├── handler.go ├── immutable.go ├── kind.go ├── kubernetes.go ├── log.go ├── metadata.go ├── pod.go ├── template.go ├── testdata │ └── warning │ │ ├── add-all-unsupported-features.yaml │ │ ├── add-unsupported-features-when-none-present.yaml │ │ ├── add-unsupported-features-when-some-present.yaml │ │ ├── remove-all-unsupported-features.yaml │ │ └── remove-single-unsupported-feature.yaml ├── validate.go ├── warning.go ├── warning_test.go └── workspace.go ├── log.go ├── mutate.go ├── mutating_cfg.go ├── validate.go └── validating_cfg.go /.clomonitor.yaml: -------------------------------------------------------------------------------- 1 | # CLOMonitor metadata file 2 | # This file must be located at the root of the repository 3 | 4 | # Checks exemptions 5 | exemptions: 6 | - check: license_scanning # Check identifier (see https://github.com/cncf/clomonitor/blob/main/docs/checks.md#exemptions) 7 | reason: "There are currently no plans moving forward to implement FOSSA or Snyk for scanning purposes." # Justification of this exemption (mandatory, it will be displayed on the UI) 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | !.git/HEAD 3 | !.git/refs/ 4 | testbin 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | deploy/deployment/** linguist-generated 2 | deploy/bundle/manifests/* linguist-generated 3 | deploy/templates/crd/bases/* linguist-generated 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global Owners 2 | * @dkwon17 @ibuziuk @akurinnoy 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | 13 | ### How To Reproduce 14 | 21 | 22 | ### Expected behavior 23 | 24 | 25 | ### Additional context 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | 13 | ### Additional context 14 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Anything that isn't a bug or feature 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does this PR do? 2 | 3 | 4 | ### What issues does this PR fix or reference? 5 | 6 | 7 | ### Is it tested? How? 8 | 9 | 10 | ### PR Checklist 11 | 12 | - [ ] E2E tests pass (when PR is ready, comment `/test v8-devworkspace-operator-e2e, v8-che-happy-path` to trigger) 13 | - [ ] `v8-devworkspace-operator-e2e`: DevWorkspace e2e test 14 | - [ ] `v8-che-happy-path`: Happy path for verification integration with Che 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | devworkspace-crds 2 | deploy/templates/crd/bases/workspace.devfile.io_devworkspaces.yaml 3 | deploy/templates/crd/bases/workspace.devfile.io_devworkspacetemplates.yaml 4 | deploy/templates/crd/bases/devfile_version 5 | deploy/current 6 | deploy/deployment/olm 7 | generated 8 | .vscode 9 | __debug_bin 10 | 11 | # Ignore cached kustomize for makefile 12 | 13 | # Binaries for programs and plugins 14 | *.exe 15 | *.exe~ 16 | *.dll 17 | *.so 18 | *.dylib 19 | bin 20 | 21 | # Test binary, build with `go test -c` 22 | *.test 23 | 24 | # Output of the go coverage tool, specifically when used with LiteIDE 25 | *.out 26 | 27 | # Kubernetes Generated files - skip generated files, except for vendored files 28 | 29 | !vendor/**/zz_generated.* 30 | 31 | # editor and IDE paraphernalia 32 | .idea 33 | *.swp 34 | *.swo 35 | *~ 36 | vendor/ 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | reviewers: 2 | - dkwon17 3 | approvers: 4 | - dkwon17 5 | component: DevWorkspace Operator 6 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: devfile.io 2 | layout: 3 | - go.kubebuilder.io/v2 4 | multigroup: true 5 | plugins: 6 | manifests.sdk.operatorframework.io/v2: {} 7 | scorecard.sdk.operatorframework.io/v2: {} 8 | projectName: devworkspace-operator 9 | repo: github.com/devfile/devworkspace-operator 10 | resources: 11 | - controller: true 12 | domain: devfile.io 13 | group: controller 14 | kind: DevWorkspaceRouting 15 | path: github.com/devfile/devworkspace-operator/apis/controller/v1alpha1 16 | version: v1alpha1 17 | - controller: true 18 | domain: devfile.io 19 | group: workspace 20 | kind: DevWorkspace 21 | path: github.com/devfile/devworkspace-operator/apis/workspace/v1alpha1 22 | version: v1alpha2 23 | - domain: devfile.io 24 | group: workspace 25 | kind: DevWorkspaceTemplate 26 | path: github.com/devfile/devworkspace-operator/apis/workspace/v1alpha1 27 | version: v1alpha2 28 | - domain: devfile.io 29 | group: controller 30 | kind: DevWorkspaceOperatorConfig 31 | path: github.com/devfile/devworkspace-operator/apis/controller/v1alpha1 32 | version: v1alpha1 33 | version: "3" 34 | -------------------------------------------------------------------------------- /apis/controller/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Package v1alpha1 contains API Schema definitions for the controller v1alpha1 API group 17 | // +k8s:deepcopy-gen=package,register 18 | // +groupName=controller.devfile.io 19 | package v1alpha1 20 | -------------------------------------------------------------------------------- /apis/controller/v1alpha1/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2025 Red Hat, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package v1alpha1 15 | 16 | import "fmt" 17 | 18 | // KeyNotFoundError returns an error if no key is found for the attribute 19 | type KeyNotFoundError struct { 20 | Key string 21 | } 22 | 23 | func (e *KeyNotFoundError) Error() string { 24 | return fmt.Sprintf("Attribute with key %q does not exist", e.Key) 25 | } 26 | -------------------------------------------------------------------------------- /build/bin/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # This is documented here: 4 | # https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines 5 | 6 | if ! whoami &>/dev/null; then 7 | if [ -w /etc/passwd ]; then 8 | echo "${USER_NAME:-devworkspace-controller}:x:$(id -u):$(id -g):${USER_NAME:-devworkspace-controller} user:${HOME}:/sbin/nologin" >> /etc/passwd 9 | fi 10 | fi 11 | 12 | exec "$@" 13 | -------------------------------------------------------------------------------- /build/bin/user_setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | # ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be) 5 | mkdir -p ${HOME} 6 | chown ${USER_UID}:0 ${HOME} 7 | chmod ug+rwx ${HOME} 8 | 9 | # runtime user will need to be able to self-insert in /etc/passwd 10 | chmod g+rw /etc/passwd 11 | 12 | # no need for this script to remain in the image after running 13 | rm $0 14 | -------------------------------------------------------------------------------- /build/buildkitd.toml: -------------------------------------------------------------------------------- 1 | [worker.oci] 2 | max-parallelism = 1 3 | -------------------------------------------------------------------------------- /build/bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=devworkspace-operator 8 | LABEL operators.operatorframework.io.bundle.channels.v1=fast 9 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.7.1+git 10 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 11 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v2 12 | 13 | # Labels for testing. 14 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 15 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 16 | 17 | # Copy files to locations specified by labels. 18 | COPY deploy/bundle/manifests /manifests/ 19 | COPY deploy/bundle/metadata /metadata/ 20 | COPY deploy/bundle/tests/scorecard /tests/scorecard/ 21 | -------------------------------------------------------------------------------- /build/index.next-digest.Dockerfile: -------------------------------------------------------------------------------- 1 | # The base image is expected to contain 2 | # /bin/opm (with a serve subcommand) and /bin/grpc_health_probe 3 | FROM quay.io/operator-framework/opm:latest 4 | 5 | # Configure the entrypoint and command 6 | ENTRYPOINT ["/bin/opm"] 7 | CMD ["serve", "/configs", "--cache-dir=/tmp/cache"] 8 | 9 | # Copy declarative config root into image at /configs and pre-populate serve cache 10 | ADD olm-catalog/next-digest /configs 11 | RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"] 12 | 13 | # Set DC-specific label for the location of the DC root directory 14 | # in the image 15 | LABEL operators.operatorframework.io.index.configs.v1=/configs 16 | -------------------------------------------------------------------------------- /build/index.next.Dockerfile: -------------------------------------------------------------------------------- 1 | # The base image is expected to contain 2 | # /bin/opm (with a serve subcommand) and /bin/grpc_health_probe 3 | FROM quay.io/operator-framework/opm:latest 4 | 5 | # Configure the entrypoint and command 6 | ENTRYPOINT ["/bin/opm"] 7 | CMD ["serve", "/configs", "--cache-dir=/tmp/cache"] 8 | 9 | # Copy declarative config root into image at /configs and pre-populate serve cache 10 | ADD olm-catalog/next /configs 11 | RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"] 12 | 13 | # Set DC-specific label for the location of the DC root directory 14 | # in the image 15 | LABEL operators.operatorframework.io.index.configs.v1=/configs 16 | -------------------------------------------------------------------------------- /build/index.release-digest.Dockerfile: -------------------------------------------------------------------------------- 1 | # The base image is expected to contain 2 | # /bin/opm (with a serve subcommand) and /bin/grpc_health_probe 3 | FROM quay.io/operator-framework/opm:latest 4 | 5 | # Configure the entrypoint and command 6 | ENTRYPOINT ["/bin/opm"] 7 | CMD ["serve", "/configs", "--cache-dir=/tmp/cache"] 8 | 9 | # Copy declarative config root into image at /configs and pre-populate serve cache 10 | ADD olm-catalog/release-digest /configs 11 | RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"] 12 | 13 | # Set DC-specific label for the location of the DC root directory 14 | # in the image 15 | LABEL operators.operatorframework.io.index.configs.v1=/configs 16 | -------------------------------------------------------------------------------- /build/index.release.Dockerfile: -------------------------------------------------------------------------------- 1 | # The base image is expected to contain 2 | # /bin/opm (with a serve subcommand) and /bin/grpc_health_probe 3 | FROM quay.io/operator-framework/opm:latest 4 | 5 | # Configure the entrypoint and command 6 | ENTRYPOINT ["/bin/opm"] 7 | CMD ["serve", "/configs", "--cache-dir=/tmp/cache"] 8 | 9 | # Copy declarative config root into image at /configs and pre-populate serve cache 10 | ADD olm-catalog/release /configs 11 | RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"] 12 | 13 | # Set DC-specific label for the location of the DC root directory 14 | # in the image 15 | LABEL operators.operatorframework.io.index.configs.v1=/configs 16 | -------------------------------------------------------------------------------- /build/make/version.mk: -------------------------------------------------------------------------------- 1 | # Resolve current git commit hash from either a .git directory 2 | # or from a local file named `.commit_hash`. The latter case 3 | # is necessary since midstream repositories might have the 4 | # .git directory one level higher and thus outside the docker 5 | # build context. 6 | ifneq (,$(wildcard .git/HEAD)) 7 | REF := $(shell cat .git/HEAD | sed 's|ref: ||') 8 | export GIT_COMMIT_ID := $(shell cat .git/$(REF)) 9 | else ifneq (,$(wildcard .commit_hash)) 10 | export GIT_COMMIT_ID := $(shell cat .commit_hash) 11 | else 12 | export GIT_COMMIT_ID := "unknown" 13 | endif 14 | 15 | # Additional build info 16 | export GO_PACKAGE_PATH := $(shell head -n1 go.mod | cut -d " " -f 2) 17 | export BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') 18 | 19 | # Architecture we're building on 20 | export ARCH := $(shell uname -m) 21 | ifeq (x86_64,$(ARCH)) 22 | export ARCH := amd64 23 | else ifeq (aarch64,$(ARCH)) 24 | export ARCH := arm64 25 | endif 26 | -------------------------------------------------------------------------------- /catalog-source.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: CatalogSource 3 | metadata: 4 | name: devworkspace-operator-catalog 5 | namespace: openshift-marketplace 6 | spec: 7 | sourceType: grpc 8 | image: quay.io/devfile/devworkspace-operator-index:next 9 | publisher: Red Hat 10 | displayName: DevWorkspace Operator Catalog 11 | updateStrategy: 12 | registryPoll: 13 | interval: 5m 14 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | github_checks: 2 | annotations: false 3 | -------------------------------------------------------------------------------- /controllers/cleanupcronjob/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Red Hat, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package controllers_test 15 | 16 | import ( 17 | "testing" 18 | 19 | . "github.com/onsi/ginkgo/v2" 20 | . "github.com/onsi/gomega" 21 | ) 22 | 23 | func TestAPIs(t *testing.T) { 24 | RegisterFailHandler(Fail) 25 | RunSpecs(t, "CleanupCronJob Controller Suite") 26 | } 27 | -------------------------------------------------------------------------------- /controllers/workspace/http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2025 Red Hat, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package controllers 15 | 16 | import "net/http" 17 | 18 | func SetupHttpClientsForTesting(client *http.Client) { 19 | httpClient = client 20 | healthCheckHttpClient = client 21 | } 22 | -------------------------------------------------------------------------------- /controllers/workspace/testdata/common-pvc-test-devworkspace.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | labels: 5 | controller.devfile.io/creator: "" 6 | spec: 7 | started: true 8 | routingClass: 'basic' 9 | template: 10 | attributes: 11 | controller.devfile.io/storage-type: common 12 | projects: 13 | - name: web-nodejs-sample 14 | git: 15 | remotes: 16 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 17 | components: 18 | - name: web-terminal 19 | container: 20 | image: quay.io/wto/web-terminal-tooling:latest 21 | memoryLimit: 512Mi 22 | mountSources: true 23 | command: 24 | - "tail" 25 | - "-f" 26 | - "/dev/null" 27 | -------------------------------------------------------------------------------- /controllers/workspace/testdata/test-devworkspace.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | labels: 5 | controller.devfile.io/creator: "" 6 | spec: 7 | started: true 8 | routingClass: 'basic' 9 | template: 10 | attributes: 11 | controller.devfile.io/storage-type: ephemeral 12 | projects: 13 | - name: web-nodejs-sample 14 | git: 15 | remotes: 16 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 17 | components: 18 | - name: web-terminal 19 | container: 20 | image: quay.io/wto/web-terminal-tooling:latest 21 | memoryLimit: 512Mi 22 | mountSources: true 23 | command: 24 | - "tail" 25 | - "-f" 26 | - "/dev/null" 27 | -------------------------------------------------------------------------------- /deploy/bundle/manifests/devworkspace-controller-edit-workspaces_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 9 | name: devworkspace-controller-edit-workspaces 10 | rules: 11 | - apiGroups: 12 | - workspace.devfile.io 13 | resources: 14 | - devworkspaces 15 | - devworkspacetemplates 16 | verbs: 17 | - create 18 | - delete 19 | - deletecollection 20 | - patch 21 | - update 22 | - apiGroups: 23 | - controller.devfile.io 24 | resources: 25 | - devworkspaceroutings 26 | - devworkspaceoperatorconfigs 27 | verbs: 28 | - create 29 | - delete 30 | - deletecollection 31 | - patch 32 | - update 33 | -------------------------------------------------------------------------------- /deploy/bundle/manifests/devworkspace-controller-manager-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | name: devworkspace-controller-manager-service 9 | spec: 10 | ports: 11 | - name: https 12 | port: 443 13 | protocol: TCP 14 | targetPort: conversion 15 | selector: 16 | app.kubernetes.io/name: devworkspace-controller 17 | app.kubernetes.io/part-of: devworkspace-operator 18 | status: 19 | loadBalancer: {} 20 | -------------------------------------------------------------------------------- /deploy/bundle/manifests/devworkspace-controller-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | name: devworkspace-controller-metrics-reader 9 | rules: 10 | - nonResourceURLs: 11 | - /metrics 12 | verbs: 13 | - get 14 | -------------------------------------------------------------------------------- /deploy/bundle/manifests/devworkspace-controller-metrics_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | name: devworkspace-controller-metrics 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 8443 13 | targetPort: metrics 14 | selector: 15 | app.kubernetes.io/name: devworkspace-controller 16 | app.kubernetes.io/part-of: devworkspace-operator 17 | status: 18 | loadBalancer: {} 19 | -------------------------------------------------------------------------------- /deploy/bundle/manifests/devworkspace-controller-view-workspaces_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 9 | rbac.authorization.k8s.io/aggregate-to-view: "true" 10 | name: devworkspace-controller-view-workspaces 11 | rules: 12 | - apiGroups: 13 | - workspace.devfile.io 14 | resources: 15 | - devworkspaces 16 | - devworkspacetemplates 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | - apiGroups: 22 | - controller.devfile.io 23 | resources: 24 | - devworkspaceroutings 25 | - devworkspaceoperatorconfigs 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | -------------------------------------------------------------------------------- /deploy/bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: devworkspace-operator 7 | operators.operatorframework.io.bundle.channels.v1: fast 8 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.7.1+git 9 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 10 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v2 11 | 12 | # Annotations for testing. 13 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 14 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 15 | -------------------------------------------------------------------------------- /deploy/default-config.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspaceOperatorConfig 2 | apiVersion: controller.devfile.io/v1alpha1 3 | metadata: 4 | name: devworkspace-operator-config 5 | namespace: ${NAMESPACE} 6 | config: 7 | routing: 8 | clusterHostSuffix: ${ROUTING_SUFFIX} 9 | defaultRoutingClass: ${DEFAULT_ROUTING} 10 | workspace: 11 | imagePullPolicy: ${PULL_POLICY} 12 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-edit-workspaces.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | name: devworkspace-controller-edit-workspaces 9 | rules: 10 | - apiGroups: 11 | - workspace.devfile.io 12 | resources: 13 | - devworkspaces 14 | - devworkspacetemplates 15 | verbs: 16 | - create 17 | - delete 18 | - deletecollection 19 | - patch 20 | - update 21 | - apiGroups: 22 | - controller.devfile.io 23 | resources: 24 | - devworkspaceroutings 25 | - devworkspaceoperatorconfigs 26 | verbs: 27 | - create 28 | - delete 29 | - deletecollection 30 | - patch 31 | - update 32 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-leader-election-role.Role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-leader-election-role 8 | namespace: devworkspace-controller 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - create 19 | - update 20 | - patch 21 | - delete 22 | - apiGroups: 23 | - "" 24 | resources: 25 | - configmaps/status 26 | verbs: 27 | - get 28 | - update 29 | - patch 30 | - apiGroups: 31 | - "" 32 | resources: 33 | - events 34 | verbs: 35 | - create 36 | - patch 37 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-leader-election-rolebinding.RoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-leader-election-rolebinding 8 | namespace: devworkspace-controller 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: Role 12 | name: devworkspace-controller-leader-election-role 13 | subjects: 14 | - kind: ServiceAccount 15 | name: devworkspace-controller-serviceaccount 16 | namespace: devworkspace-controller 17 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-manager-service.Service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-manager-service 8 | namespace: devworkspace-controller 9 | spec: 10 | ports: 11 | - name: https 12 | port: 443 13 | protocol: TCP 14 | targetPort: conversion 15 | selector: 16 | app.kubernetes.io/name: devworkspace-controller 17 | app.kubernetes.io/part-of: devworkspace-operator 18 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-metrics-reader.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-metrics-reader 8 | rules: 9 | - nonResourceURLs: 10 | - /metrics 11 | verbs: 12 | - get 13 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-metrics.Service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-metrics 8 | namespace: devworkspace-controller 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 8443 13 | targetPort: metrics 14 | selector: 15 | app.kubernetes.io/name: devworkspace-controller 16 | app.kubernetes.io/part-of: devworkspace-operator 17 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-proxy-role.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-proxy-role 8 | rules: 9 | - apiGroups: 10 | - authentication.k8s.io 11 | resources: 12 | - tokenreviews 13 | verbs: 14 | - create 15 | - apiGroups: 16 | - authorization.k8s.io 17 | resources: 18 | - subjectaccessreviews 19 | verbs: 20 | - create 21 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-proxy-rolebinding.ClusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-proxy-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: devworkspace-controller-proxy-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: devworkspace-controller-serviceaccount 15 | namespace: devworkspace-controller 16 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-rolebinding.ClusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: devworkspace-controller-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: devworkspace-controller-serviceaccount 15 | namespace: devworkspace-controller 16 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-selfsigned-issuer.Issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Issuer 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-selfsigned-issuer 8 | namespace: devworkspace-controller 9 | spec: 10 | selfSigned: {} 11 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-serviceaccount.ServiceAccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-serviceaccount 8 | namespace: devworkspace-controller 9 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-serving-cert.Certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-serving-cert 8 | namespace: devworkspace-controller 9 | spec: 10 | dnsNames: 11 | - devworkspace-webhookserver.devworkspace-controller.svc 12 | - devworkspace-webhookserver.devworkspace-controller.svc.cluster.local 13 | - devworkspace-controller-manager-service.devworkspace-controller.svc 14 | - devworkspace-controller-manager-service.devworkspace-controller.svc.cluster.local 15 | issuerRef: 16 | kind: Issuer 17 | name: devworkspace-controller-selfsigned-issuer 18 | secretName: devworkspace-operator-webhook-cert 19 | -------------------------------------------------------------------------------- /deploy/deployment/kubernetes/objects/devworkspace-controller-view-workspaces.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | rbac.authorization.k8s.io/aggregate-to-view: "true" 9 | name: devworkspace-controller-view-workspaces 10 | rules: 11 | - apiGroups: 12 | - workspace.devfile.io 13 | resources: 14 | - devworkspaces 15 | - devworkspacetemplates 16 | verbs: 17 | - get 18 | - list 19 | - watch 20 | - apiGroups: 21 | - controller.devfile.io 22 | resources: 23 | - devworkspaceroutings 24 | - devworkspaceoperatorconfigs 25 | verbs: 26 | - get 27 | - list 28 | - watch 29 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-edit-workspaces.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | name: devworkspace-controller-edit-workspaces 9 | rules: 10 | - apiGroups: 11 | - workspace.devfile.io 12 | resources: 13 | - devworkspaces 14 | - devworkspacetemplates 15 | verbs: 16 | - create 17 | - delete 18 | - deletecollection 19 | - patch 20 | - update 21 | - apiGroups: 22 | - controller.devfile.io 23 | resources: 24 | - devworkspaceroutings 25 | - devworkspaceoperatorconfigs 26 | verbs: 27 | - create 28 | - delete 29 | - deletecollection 30 | - patch 31 | - update 32 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-leader-election-role.Role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-leader-election-role 8 | namespace: devworkspace-controller 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - create 19 | - update 20 | - patch 21 | - delete 22 | - apiGroups: 23 | - "" 24 | resources: 25 | - configmaps/status 26 | verbs: 27 | - get 28 | - update 29 | - patch 30 | - apiGroups: 31 | - "" 32 | resources: 33 | - events 34 | verbs: 35 | - create 36 | - patch 37 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-leader-election-rolebinding.RoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-leader-election-rolebinding 8 | namespace: devworkspace-controller 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: Role 12 | name: devworkspace-controller-leader-election-role 13 | subjects: 14 | - kind: ServiceAccount 15 | name: devworkspace-controller-serviceaccount 16 | namespace: devworkspace-controller 17 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-manager-service.Service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | service.beta.openshift.io/serving-cert-secret-name: devworkspace-webhooks-tls 6 | labels: 7 | app.kubernetes.io/name: devworkspace-controller 8 | app.kubernetes.io/part-of: devworkspace-operator 9 | name: devworkspace-controller-manager-service 10 | namespace: devworkspace-controller 11 | spec: 12 | ports: 13 | - name: https 14 | port: 443 15 | protocol: TCP 16 | targetPort: conversion 17 | selector: 18 | app.kubernetes.io/name: devworkspace-controller 19 | app.kubernetes.io/part-of: devworkspace-operator 20 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-metrics-reader.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-metrics-reader 8 | rules: 9 | - nonResourceURLs: 10 | - /metrics 11 | verbs: 12 | - get 13 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-metrics.Service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-metrics 8 | namespace: devworkspace-controller 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 8443 13 | targetPort: metrics 14 | selector: 15 | app.kubernetes.io/name: devworkspace-controller 16 | app.kubernetes.io/part-of: devworkspace-operator 17 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-proxy-role.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-proxy-role 8 | rules: 9 | - apiGroups: 10 | - authentication.k8s.io 11 | resources: 12 | - tokenreviews 13 | verbs: 14 | - create 15 | - apiGroups: 16 | - authorization.k8s.io 17 | resources: 18 | - subjectaccessreviews 19 | verbs: 20 | - create 21 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-proxy-rolebinding.ClusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-proxy-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: devworkspace-controller-proxy-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: devworkspace-controller-serviceaccount 15 | namespace: devworkspace-controller 16 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-rolebinding.ClusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: devworkspace-controller-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: devworkspace-controller-serviceaccount 15 | namespace: devworkspace-controller 16 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-serviceaccount.ServiceAccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | name: devworkspace-controller-serviceaccount 8 | namespace: devworkspace-controller 9 | -------------------------------------------------------------------------------- /deploy/deployment/openshift/objects/devworkspace-controller-view-workspaces.ClusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: devworkspace-controller 6 | app.kubernetes.io/part-of: devworkspace-operator 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | rbac.authorization.k8s.io/aggregate-to-view: "true" 9 | name: devworkspace-controller-view-workspaces 10 | rules: 11 | - apiGroups: 12 | - workspace.devfile.io 13 | resources: 14 | - devworkspaces 15 | - devworkspacetemplates 16 | verbs: 17 | - get 18 | - list 19 | - watch 20 | - apiGroups: 21 | - controller.devfile.io 22 | resources: 23 | - devworkspaceroutings 24 | - devworkspaceoperatorconfigs 25 | verbs: 26 | - get 27 | - list 28 | - watch 29 | -------------------------------------------------------------------------------- /deploy/templates/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | bases: 2 | - ../components/manager 3 | - ../components/rbac 4 | - ../crd 5 | 6 | generatorOptions: 7 | disableNameSuffixHash: true 8 | 9 | patchesStrategicMerge: 10 | - manager_image_patch.yaml 11 | - webhooks_name_env_patch.yaml 12 | 13 | vars: 14 | - name: OPERATOR_NAMESPACE 15 | objref: 16 | kind: Deployment 17 | group: apps 18 | version: v1 19 | name: manager 20 | fieldref: 21 | fieldpath: metadata.namespace 22 | -------------------------------------------------------------------------------- /deploy/templates/base/manager_image_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch sets the image used for deployment according to environment variables. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: devworkspace-controller 12 | image: ${DWO_IMG} 13 | imagePullPolicy: Always 14 | env: 15 | - name: RELATED_IMAGE_devworkspace_webhook_server 16 | value: ${DWO_IMG} 17 | - name: RELATED_IMAGE_kube_rbac_proxy 18 | value: ${RBAC_PROXY_IMAGE} 19 | - name: RELATED_IMAGE_project_clone 20 | value: ${PROJECT_CLONE_IMG} 21 | - name: kube-rbac-proxy 22 | image: ${RBAC_PROXY_IMAGE} 23 | -------------------------------------------------------------------------------- /deploy/templates/base/webhooks_name_env_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch sets the default name for the webhooks secret name. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: devworkspace-controller 12 | env: 13 | - name: WEBHOOK_SECRET_NAME 14 | value: devworkspace-webhookserver-tls 15 | -------------------------------------------------------------------------------- /deploy/templates/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: ${NAMESPACE} 3 | 4 | # Prefix for names of all resources created by this kustomization 5 | namePrefix: devworkspace-controller- 6 | 7 | # Labels to add to all resources and selectors. 8 | commonLabels: 9 | app.kubernetes.io/name: devworkspace-controller 10 | app.kubernetes.io/part-of: devworkspace-operator 11 | 12 | bases: 13 | - ../base 14 | - ../components/cert-manager 15 | 16 | patchesStrategicMerge: 17 | - manager_certmanager_patch.yaml 18 | - crd_webhooks_patch.yaml 19 | 20 | configurations: 21 | - kustomizeconfig.yaml 22 | 23 | vars: 24 | - name: WEBHOOK_CA_SECRET_NAME 25 | objref: 26 | kind: Certificate 27 | group: cert-manager.io 28 | version: v1 29 | name: serving-cert 30 | fieldref: 31 | fieldpath: spec.secretName 32 | - name: CERTIFICATE_NAMESPACE 33 | objref: 34 | kind: Certificate 35 | group: cert-manager.io 36 | version: v1 37 | name: serving-cert 38 | fieldref: 39 | fieldpath: metadata.namespace 40 | - name: CERTIFICATE_NAME 41 | objref: 42 | kind: Certificate 43 | group: cert-manager.io 44 | version: v1 45 | name: serving-cert 46 | -------------------------------------------------------------------------------- /deploy/templates/cert-manager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | varReference: 2 | - kind: Deployment 3 | path: spec/template/spec/volumes/secret/secretName 4 | -------------------------------------------------------------------------------- /deploy/templates/cert-manager/manager_certmanager_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch adds an environment variable to the workspace deployment 2 | # to allow it to figure out which secret to use for the webhooks deployment 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: devworkspace-controller 13 | env: 14 | - name: WEBHOOK_SECRET_NAME 15 | value: $(WEBHOOK_CA_SECRET_NAME) 16 | volumeMounts: 17 | - mountPath: /tmp/k8s-webhook-server/serving-certs 18 | name: webhook-tls-certs 19 | readOnly: true 20 | volumes: 21 | - name: webhook-tls-certs 22 | secret: 23 | defaultMode: 420 24 | secretName: $(WEBHOOK_CA_SECRET_NAME) 25 | -------------------------------------------------------------------------------- /deploy/templates/components/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - self-signed-certificates.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /deploy/templates/components/cert-manager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | nameReference: 2 | - kind: Issuer 3 | group: cert-manager.io 4 | fieldSpecs: 5 | - kind: Certificate 6 | group: cert-manager.io 7 | path: spec/issuerRef/name 8 | 9 | varReference: 10 | - kind: Certificate 11 | group: cert-manager.io 12 | path: spec/secretName 13 | - kind: Certificate 14 | group: cert-manager.io 15 | path: spec/dnsNames 16 | -------------------------------------------------------------------------------- /deploy/templates/components/cert-manager/self-signed-certificates.yaml: -------------------------------------------------------------------------------- 1 | # Create a self-signed issuer for development 2 | apiVersion: cert-manager.io/v1 3 | kind: Issuer 4 | metadata: 5 | name: selfsigned-issuer 6 | namespace: system 7 | spec: 8 | selfSigned: {} 9 | --- 10 | apiVersion: cert-manager.io/v1 11 | kind: Certificate 12 | metadata: 13 | name: serving-cert 14 | namespace: system 15 | spec: 16 | secretName: devworkspace-operator-webhook-cert 17 | dnsNames: 18 | # Note: service name is hard-coded in pkg/webhook/server/server.go 19 | - devworkspace-webhookserver.$(OPERATOR_NAMESPACE).svc 20 | - devworkspace-webhookserver.$(OPERATOR_NAMESPACE).svc.cluster.local 21 | - devworkspace-controller-manager-service.$(OPERATOR_NAMESPACE).svc 22 | - devworkspace-controller-manager-service.$(OPERATOR_NAMESPACE).svc.cluster.local 23 | issuerRef: 24 | kind: Issuer 25 | name: selfsigned-issuer 26 | -------------------------------------------------------------------------------- /deploy/templates/components/csv/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - clusterserviceversion.yaml 3 | -------------------------------------------------------------------------------- /deploy/templates/components/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | - serviceaccount.yaml 4 | - service.yaml 5 | - service-metrics.yaml 6 | 7 | vars: 8 | - name: CONTROLLER_SERVICE_ACCOUNT 9 | objref: 10 | kind: ServiceAccount 11 | version: v1 12 | name: serviceaccount 13 | 14 | configurations: 15 | - kustomizeconfig.yaml 16 | -------------------------------------------------------------------------------- /deploy/templates/components/manager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | varReference: 2 | - kind: Deployment 3 | group: apps 4 | path: spec/template/spec/serviceAccountName 5 | -------------------------------------------------------------------------------- /deploy/templates/components/manager/service-metrics.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: metrics 5 | namespace: system 6 | spec: 7 | ports: 8 | - name: metrics 9 | port: 8443 10 | targetPort: metrics 11 | selector: 12 | app.kubernetes.io/name: devworkspace-controller 13 | app.kubernetes.io/part-of: devworkspace-operator 14 | -------------------------------------------------------------------------------- /deploy/templates/components/manager/service.yaml: -------------------------------------------------------------------------------- 1 | # This service cannot be modified; added ports will be removed in an OLM install, 2 | # and changing the name defined below will result in duplicate services being created 3 | # when installed via OLM. This service is also necessary for conversion webhooks to 4 | # be created successfully in OLM. 5 | # In other words, take great care in modifying the object below. 6 | # See issue https://github.com/operator-framework/operator-lifecycle-manager/issues/2233 7 | # for details. 8 | apiVersion: v1 9 | kind: Service 10 | metadata: 11 | name: manager-service 12 | namespace: system 13 | spec: 14 | ports: 15 | - name: https 16 | port: 443 17 | targetPort: conversion 18 | protocol: TCP 19 | selector: 20 | app.kubernetes.io/name: devworkspace-controller 21 | app.kubernetes.io/part-of: devworkspace-operator 22 | -------------------------------------------------------------------------------- /deploy/templates/components/manager/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: serviceaccount 6 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/auth_proxy_client_cluster_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/auth_proxy_cluster_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/auth_proxy_cluster_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: $(CONTROLLER_SERVICE_ACCOUNT) 12 | namespace: system 13 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/edit_workspaces_cluster_role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 9 | name: edit-workspaces 10 | rules: 11 | - apiGroups: 12 | - workspace.devfile.io 13 | resources: 14 | - devworkspaces 15 | - devworkspacetemplates 16 | verbs: 17 | - create 18 | - delete 19 | - deletecollection 20 | - patch 21 | - update 22 | - apiGroups: 23 | - controller.devfile.io 24 | resources: 25 | - devworkspaceroutings 26 | - devworkspaceoperatorconfigs 27 | verbs: 28 | - create 29 | - delete 30 | - deletecollection 31 | - patch 32 | - update 33 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Cluster-edit and view roles for devworkspaces and related resources 7 | - edit_workspaces_cluster_role.yaml 8 | - view_workspaces_cluster_role.yaml 9 | # Clusterroles for metrics auth proxy 10 | - auth_proxy_cluster_role.yaml 11 | - auth_proxy_cluster_role_binding.yaml 12 | - auth_proxy_client_cluster_role.yaml 13 | 14 | configurations: 15 | - kustomizeconfig.yaml 16 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | varReference: 2 | - kind: ClusterRoleBinding 3 | group: rbac.authorization.k8s.io 4 | path: subjects/name 5 | - kind: RoleBinding 6 | group: rbac.authorization.k8s.io 7 | path: subjects/name 8 | 9 | namespace: 10 | - kind: ClusterRoleBinding 11 | group: rbac.authorization.k8s.io 12 | path: subjects/namespace 13 | - kind: RoleBinding 14 | group: rbac.authorization.k8s.io 15 | path: subjects/namespace 16 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | - patch 34 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: $(CONTROLLER_SERVICE_ACCOUNT) 12 | namespace: system 13 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: $(CONTROLLER_SERVICE_ACCOUNT) 12 | namespace: system 13 | -------------------------------------------------------------------------------- /deploy/templates/components/rbac/view_workspaces_cluster_role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | rbac.authorization.k8s.io/aggregate-to-view: "true" 9 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 10 | name: view-workspaces 11 | rules: 12 | - apiGroups: 13 | - workspace.devfile.io 14 | resources: 15 | - devworkspaces 16 | - devworkspacetemplates 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | - apiGroups: 22 | - controller.devfile.io 23 | resources: 24 | - devworkspaceroutings 25 | - devworkspaceoperatorconfigs 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | -------------------------------------------------------------------------------- /deploy/templates/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by deploy/templates/default 4 | resources: 5 | - bases/controller.devfile.io_devworkspaceroutings.yaml 6 | - bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml 7 | - bases/workspace.devfile.io_devworkspaces.yaml 8 | - bases/workspace.devfile.io_devworkspacetemplates.yaml 9 | # +kubebuilder:scaffold:crdkustomizeresource 10 | 11 | # the following config is for teaching kustomize how to do kustomization for CRDs. 12 | configurations: 13 | - kustomizeconfig.yaml 14 | -------------------------------------------------------------------------------- /deploy/templates/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhook/clientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhook/clientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /deploy/templates/olm/crd_webhooks_patch.yaml: -------------------------------------------------------------------------------- 1 | # Add webhooks to the devfile/api CRDs 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: devworkspaces.workspace.devfile.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | conversionReviewVersions: ["v1"] 11 | clientConfig: 12 | service: 13 | namespace: system 14 | name: devworkspace-controller-manager-service 15 | path: /convert 16 | port: 443 17 | # caBundle will be filled by Service CA operator 18 | --- 19 | # Add webhooks to the devfile/api CRDs 20 | apiVersion: apiextensions.k8s.io/v1 21 | kind: CustomResourceDefinition 22 | metadata: 23 | name: devworkspacetemplates.workspace.devfile.io 24 | spec: 25 | conversion: 26 | strategy: Webhook 27 | webhook: 28 | conversionReviewVersions: ["v1"] 29 | clientConfig: 30 | service: 31 | namespace: system 32 | name: devworkspace-controller-manager-service 33 | path: /convert 34 | port: 443 35 | # caBundle will be filled by Service CA operator 36 | -------------------------------------------------------------------------------- /deploy/templates/olm/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # The prefixed base has to be used separately to add namePrefix to all resources, 2 | # as namePrefix cannot be disabled for specific types, and we need the 3 | # CSV's .metadata.name to be unchanged 4 | 5 | bases: 6 | - prefixed 7 | - ../components/csv 8 | 9 | patchesStrategicMerge: 10 | - crd_webhooks_patch.yaml 11 | -------------------------------------------------------------------------------- /deploy/templates/olm/prefixed/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This has to be a separate kustomize file, since namePrefix below cannot be 2 | # disabled for specific types, and we need the CSV's .metadata.name to be unchanged 3 | 4 | # Labels to add to all resources and selectors. 5 | commonLabels: 6 | app.kubernetes.io/name: devworkspace-controller 7 | app.kubernetes.io/part-of: devworkspace-operator 8 | 9 | # Prefix for names of all resources created by this kustomization 10 | namePrefix: devworkspace-controller- 11 | 12 | bases: 13 | - ../../base 14 | -------------------------------------------------------------------------------- /deploy/templates/service-ca/crd_webhooks_patch.yaml: -------------------------------------------------------------------------------- 1 | # Add webhooks to the devfile/api CRDs 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: devworkspaces.workspace.devfile.io 6 | annotations: 7 | service.beta.openshift.io/inject-cabundle: "true" 8 | spec: 9 | conversion: 10 | strategy: Webhook 11 | webhook: 12 | conversionReviewVersions: ["v1"] 13 | clientConfig: 14 | service: 15 | namespace: system 16 | name: devworkspace-controller-manager-service 17 | path: /convert 18 | port: 443 19 | # caBundle will be filled by Service CA operator 20 | --- 21 | # Add webhooks to the devfile/api CRDs 22 | apiVersion: apiextensions.k8s.io/v1 23 | kind: CustomResourceDefinition 24 | metadata: 25 | name: devworkspacetemplates.workspace.devfile.io 26 | annotations: 27 | service.beta.openshift.io/inject-cabundle: "true" 28 | spec: 29 | conversion: 30 | strategy: Webhook 31 | webhook: 32 | conversionReviewVersions: ["v1"] 33 | clientConfig: 34 | service: 35 | namespace: system 36 | name: devworkspace-controller-manager-service 37 | path: /convert 38 | port: 443 39 | # caBundle will be filled by Service CA operator 40 | -------------------------------------------------------------------------------- /deploy/templates/service-ca/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: ${NAMESPACE} 3 | 4 | # Prefix for names of all resources created by this kustomization 5 | namePrefix: devworkspace-controller- 6 | 7 | # Labels to add to all resources and selectors. 8 | commonLabels: 9 | app.kubernetes.io/name: devworkspace-controller 10 | app.kubernetes.io/part-of: devworkspace-operator 11 | 12 | bases: 13 | - ../base 14 | 15 | patchesStrategicMerge: 16 | - crd_webhooks_patch.yaml 17 | - manager_service_ca_patch.yaml 18 | - service_cert_patch.yaml 19 | -------------------------------------------------------------------------------- /deploy/templates/service-ca/manager_service_ca_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: devworkspace-controller 11 | volumeMounts: 12 | - mountPath: /tmp/k8s-webhook-server/serving-certs 13 | name: webhook-tls-certs 14 | readOnly: true 15 | volumes: 16 | - name: webhook-tls-certs 17 | secret: 18 | defaultMode: 420 19 | secretName: devworkspace-webhooks-tls 20 | -------------------------------------------------------------------------------- /deploy/templates/service-ca/service_cert_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: manager-service 5 | namespace: system 6 | annotations: 7 | service.beta.openshift.io/serving-cert-secret-name: devworkspace-webhooks-tls 8 | -------------------------------------------------------------------------------- /docs/release/prerelease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/docs/release/prerelease.png -------------------------------------------------------------------------------- /docs/release/release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/docs/release/release.png -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | -------------------------------------------------------------------------------- /img/apply-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/apply-demo.gif -------------------------------------------------------------------------------- /img/devworkspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/devworkspace.png -------------------------------------------------------------------------------- /img/dwo-on-developer-sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/dwo-on-developer-sandbox.png -------------------------------------------------------------------------------- /img/get-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/get-demo.gif -------------------------------------------------------------------------------- /img/intellij.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/intellij.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/logo.png -------------------------------------------------------------------------------- /img/vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/vscode.png -------------------------------------------------------------------------------- /img/wto-on-developer-sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfile/devworkspace-operator/74a430a193362592f064177a662b4cd2df3b2245/img/wto-on-developer-sandbox.png -------------------------------------------------------------------------------- /internal/map/map.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package maputils 17 | 18 | func Append(target map[string]string, key, value string) map[string]string { 19 | if target == nil { 20 | target = map[string]string{} 21 | } 22 | target[key] = value 23 | return target 24 | } 25 | 26 | // Equal compares string maps for equality, regardless of order. Note that it treats 27 | // a nil map as equal to an empty (but not nil) map. 28 | func Equal(a, b map[string]string) bool { 29 | if len(a) != len(b) { 30 | return false 31 | } 32 | for k, v := range a { 33 | if bval, ok := b[k]; !ok || bval != v { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /license_header.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2025 Red Hat, Inc. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | -------------------------------------------------------------------------------- /olm-catalog/next-digest/channel.yaml: -------------------------------------------------------------------------------- 1 | schema: olm.channel 2 | package: devworkspace-operator 3 | name: next 4 | -------------------------------------------------------------------------------- /olm-catalog/next-digest/package.yaml: -------------------------------------------------------------------------------- 1 | schema: olm.package 2 | defaultChannel: next 3 | name: devworkspace-operator 4 | -------------------------------------------------------------------------------- /olm-catalog/next/channel.yaml: -------------------------------------------------------------------------------- 1 | schema: olm.channel 2 | package: devworkspace-operator 3 | name: next 4 | -------------------------------------------------------------------------------- /olm-catalog/next/package.yaml: -------------------------------------------------------------------------------- 1 | schema: olm.package 2 | defaultChannel: next 3 | name: devworkspace-operator 4 | -------------------------------------------------------------------------------- /olm-catalog/release-digest/package.yaml: -------------------------------------------------------------------------------- 1 | schema: olm.package 2 | defaultChannel: fast 3 | name: devworkspace-operator 4 | -------------------------------------------------------------------------------- /olm-catalog/release/package.yaml: -------------------------------------------------------------------------------- 1 | schema: olm.package 2 | defaultChannel: fast 3 | name: devworkspace-operator 4 | -------------------------------------------------------------------------------- /pkg/common/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2025 Red Hat, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package common 15 | 16 | import ( 17 | dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 18 | controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" 19 | ) 20 | 21 | type DevWorkspaceWithConfig struct { 22 | *dw.DevWorkspace `json:",inline"` 23 | Config *controllerv1alpha1.OperatorConfiguration `json:"resolvedConfig"` 24 | } 25 | -------------------------------------------------------------------------------- /pkg/config/configmap/doc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // Package config is used by components to get configuration. 16 | // 17 | // Typically each configuration property has the default value. 18 | // Default value is supposed to be overridden via config map. 19 | // 20 | // There is the following configuration names convention: 21 | // - words are lower-cased 22 | // - . is used to separate subcomponents 23 | // - _ is used to separate words in the component name 24 | // 25 | 26 | package configmap 27 | -------------------------------------------------------------------------------- /pkg/library/annotate/annotations.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package annotate 17 | -------------------------------------------------------------------------------- /pkg/library/constants/constants.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Package constants contains constants related to the devfile API spec (e.g. reserved env vars) 17 | package constants 18 | 19 | const ( 20 | ProjectsRootEnvVar = "PROJECTS_ROOT" 21 | ProjectsSourceEnvVar = "PROJECT_SOURCE" 22 | ProjectsVolumeName = "projects" 23 | ) 24 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/component/ignores-non-container-components.yaml: -------------------------------------------------------------------------------- 1 | name: "Ignores non-container components" 2 | 3 | input: 4 | components: 5 | - name: testing-container-1 6 | container: 7 | image: testing-image-1 8 | mountSources: false # isolate test to not include volumes 9 | memoryRequest: "-1" # isolate test to not include this field 10 | memoryLimit: "-1" # isolate test to not include this field 11 | cpuRequest: "-1" # isolate test to not include this field 12 | cpuLimit: "-1" # isolate test to not include this field 13 | - name: test-volume 14 | volume: {} 15 | output: 16 | podAdditions: 17 | containers: 18 | - name: testing-container-1 19 | image: testing-image-1 20 | imagePullPolicy: Always 21 | env: 22 | - name: "DEVWORKSPACE_COMPONENT_NAME" 23 | value: "testing-container-1" 24 | resources: 25 | requests: 26 | memory: "-1" 27 | cpu: "-1" 28 | limits: 29 | memory: "-1" 30 | cpu: "-1" 31 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/container/resources-error-cpu-limit-less-than-request.yaml: -------------------------------------------------------------------------------- 1 | name: "Checks that CPU limit is greater than request" 2 | 3 | input: 4 | components: 5 | - name: testing-container 6 | container: 7 | image: testing-image 8 | cpuLimit: 500m 9 | cpuRequest: 620m 10 | 11 | output: 12 | errRegexp: "CPU request \\(620m\\) must be less than or equal to limit \\(500m\\)" 13 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/container/resources-error-invalid-memorylimit.yaml: -------------------------------------------------------------------------------- 1 | name: "Checks for invalid memory limit" 2 | 3 | input: 4 | components: 5 | - name: testing-container 6 | container: 7 | image: testing-image 8 | memoryLimit: "x" 9 | 10 | output: 11 | errRegexp: "failed to parse memory limit.*" 12 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/container/resources-error-memory-limit-less-than-requst.yaml: -------------------------------------------------------------------------------- 1 | name: "Checks that memory limit is greater than request" 2 | 3 | input: 4 | components: 5 | - name: testing-container 6 | container: 7 | image: testing-image 8 | memoryLimit: 256Mi 9 | memoryRequest: 1Gi 10 | 11 | output: 12 | errRegexp: "memory request \\(1Gi\\) must be less than or equal to limit \\(256Mi\\)" 13 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/container/resources-fixes-memory-limit-less-than-default-request.yaml: -------------------------------------------------------------------------------- 1 | name: "Reduces default memory request when limit is lower than the default" 2 | 3 | input: 4 | components: 5 | - name: testing-container 6 | container: 7 | image: testing-image 8 | memoryLimit: 1Mi 9 | # memoryRequest: 64Mi default (something greater than 1Mi) 10 | mountSources: false 11 | 12 | output: 13 | podAdditions: 14 | containers: 15 | - name: testing-container 16 | image: testing-image 17 | imagePullPolicy: Always 18 | resources: 19 | requests: 20 | memory: "1Mi" 21 | limits: 22 | memory: "1Mi" 23 | env: 24 | - name: "DEVWORKSPACE_COMPONENT_NAME" 25 | value: "testing-container" 26 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/parent/error-has-parent.yaml: -------------------------------------------------------------------------------- 1 | name: "Checks for flattened devfile (parent)" 2 | 3 | input: 4 | parent: 5 | registryUrl: "has-parent" 6 | components: 7 | - name: testing-container 8 | container: 9 | image: testing-image 10 | 11 | output: 12 | errRegexp: "devfile is not flattened" 13 | -------------------------------------------------------------------------------- /pkg/library/container/testdata/plugin/error-has-plugins.yaml: -------------------------------------------------------------------------------- 1 | name: "Checks for flattened devfile (plugins)" 2 | 3 | input: 4 | components: 5 | - name: testing-container 6 | container: 7 | image: testing-image 8 | - name: my-plugin 9 | plugin: 10 | id: "test-id" 11 | 12 | output: 13 | errRegexp: "devfile is not flattened" 14 | -------------------------------------------------------------------------------- /pkg/library/env/testdata/workspace-env/adds-workspaceEnv-successfully-when-duplicate.yaml: -------------------------------------------------------------------------------- 1 | name: "Resplves workspace environment variables when env vars are duplicated" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: plugin-a 7 | attributes: 8 | workspaceEnv: 9 | - name: TEST_ENV_1 10 | value: TEST_VAL_1 11 | - name: TEST_ENV_2 12 | value: TEST_VAL_2 13 | container: 14 | name: test-container-a 15 | image: test-image-a 16 | - name: plugin-b 17 | attributes: 18 | workspaceEnv: 19 | - name: TEST_ENV_1 20 | value: TEST_VAL_1 21 | container: 22 | name: test-container-b 23 | image: test-image-b 24 | 25 | output: 26 | workspaceEnv: 27 | - name: TEST_ENV_1 28 | value: TEST_VAL_1 29 | - name: TEST_ENV_2 30 | value: TEST_VAL_2 31 | -------------------------------------------------------------------------------- /pkg/library/env/testdata/workspace-env/adds-workspaceEnv-to-all-containers.yaml: -------------------------------------------------------------------------------- 1 | name: "Finds environment variables in a component" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | 7 | - name: test-plugin 8 | plugin: 9 | uri: "https://my-plugin.io/test" 10 | 11 | - name: regular-container 12 | container: 13 | image: test-image-regular 14 | 15 | - name: init-container 16 | container: 17 | image: test-image-init 18 | 19 | - name: plugin-a 20 | attributes: 21 | controller.devfile.io/imported-by: test-plugin 22 | workspaceEnv: 23 | - name: TEST_ENV_1 24 | value: TEST_VAL_1 25 | - name: TEST_ENV_2 26 | value: TEST_VAL_2 27 | container: 28 | name: test-container 29 | image: test-image 30 | 31 | commands: 32 | - id: make-init 33 | apply: 34 | component: init-container 35 | 36 | events: 37 | prestart: [make-init] 38 | 39 | output: 40 | workspaceEnv: 41 | - name: TEST_ENV_1 42 | value: TEST_VAL_1 43 | - name: TEST_ENV_2 44 | value: TEST_VAL_2 45 | -------------------------------------------------------------------------------- /pkg/library/env/testdata/workspace-env/adds-workspaceEnv-written-as-json.yaml: -------------------------------------------------------------------------------- 1 | name: "Finds workspace environment variables when written as JSON" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | 7 | - name: test-plugin 8 | plugin: 9 | uri: "https://my-plugin.io/test" 10 | 11 | - name: regular-container 12 | container: 13 | image: test-image-regular 14 | 15 | - name: init-container 16 | container: 17 | image: test-image-init 18 | 19 | - name: plugin-a 20 | attributes: 21 | workspaceEnv: 22 | [{"name": "TEST_ENV_1", 23 | "value": "TEST_VAL_1"}, 24 | {"name": "TEST_ENV_2", 25 | "value": "TEST_VAL_2"}] 26 | container: 27 | name: test-container 28 | image: test-image 29 | 30 | commands: 31 | - id: make-init 32 | apply: 33 | component: init-container 34 | 35 | events: 36 | prestart: [make-init] 37 | 38 | output: 39 | workspaceEnv: 40 | - name: TEST_ENV_1 41 | value: TEST_VAL_1 42 | - name: TEST_ENV_2 43 | value: TEST_VAL_2 44 | -------------------------------------------------------------------------------- /pkg/library/env/testdata/workspace-env/error_duplicated-workspaceEnv-with-different-value.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns an error when there are conflicting definitions of workspace env var" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-container 7 | attributes: 8 | workspaceEnv: 9 | - name: TEST_ENV_2 10 | value: NOT_TEST_VAL_2 11 | container: 12 | image: test-image 13 | - name: plugin-a 14 | attributes: 15 | controller.devfile.io/imported-by: test-plugin 16 | workspaceEnv: 17 | - name: TEST_ENV_1 18 | value: TEST_VAL_1 19 | - name: TEST_ENV_2 20 | value: TEST_VAL_2 21 | container: 22 | name: test-container 23 | image: test-image 24 | 25 | output: 26 | errRegexp: "conflicting definition of environment variable TEST_ENV_2 in component test-container and component test-plugin" 27 | -------------------------------------------------------------------------------- /pkg/library/env/testdata/workspace-env/error_workspaceEnv-formatted-wrong.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns an error when workspaceEnv cannot be read as []EnvVar" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: plugin-a 7 | attributes: 8 | # Note this fails to deserialize because it's just a yaml string, not a list. 9 | # JSON can be used here if you remove the pipe 10 | workspaceEnv: | 11 | [{"name": "TEST_ENV_1", 12 | "value": "TEST_VAL_1"}, 13 | {"name": "TEST_ENV_2", 14 | "value": "TEST_VAL_2"}] 15 | container: 16 | name: test-container 17 | image: test-image 18 | 19 | output: 20 | errRegexp: "failed to read attribute workspaceEnv on component plugin-a" 21 | -------------------------------------------------------------------------------- /pkg/library/flatten/common.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package flatten 17 | 18 | import dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 19 | 20 | func DevWorkspaceIsFlattened(devworkspace *dw.DevWorkspaceTemplateSpec, contributions []dw.ComponentContribution) bool { 21 | if devworkspace == nil { 22 | return len(contributions) == 0 23 | } 24 | 25 | if devworkspace.Parent != nil { 26 | return false 27 | } 28 | 29 | if len(contributions) > 0 { 30 | return false 31 | } 32 | 33 | for _, component := range devworkspace.Components { 34 | if component.Plugin != nil { 35 | return false 36 | } 37 | } 38 | 39 | return true 40 | } 41 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/disabled/error_duplicate-editors.yaml: -------------------------------------------------------------------------------- 1 | name: "Duplicate editors" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: editor-plugin-1 7 | plugin: 8 | kubernetes: 9 | name: editor-plugin-1 10 | - name: editor-plugin-2 11 | plugin: 12 | kubernetes: 13 | name: editor-plugin-2 14 | plugins: 15 | editor-plugin-1: 16 | kind: DevWorkspaceTemplate 17 | apiVersion: workspace.devfile.io/v1alpha2 18 | metadata: 19 | name: editor-plugin-1 20 | labels: 21 | "devworkspace.devfile.io/editor-name": "test-editor-1" 22 | spec: 23 | components: 24 | - name: editor-container 25 | container: 26 | image: "test-image" 27 | editor-plugin-2: 28 | kind: DevWorkspaceTemplate 29 | apiVersion: workspace.devfile.io/v1alpha2 30 | metadata: 31 | name: plugin-sidecar 32 | labels: 33 | "devworkspace.devfile.io/editor-name": "test-editor-1" 34 | spec: 35 | components: 36 | - name: plugin-container 37 | container: 38 | image: "test-image" 39 | 40 | output: 41 | errRegexp: 'multiple components define the same editor.*' 42 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/disabled/error_multiple-editors.yaml: -------------------------------------------------------------------------------- 1 | name: "Multiple editors in one workspace" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: editor-plugin-1 7 | plugin: 8 | kubernetes: 9 | name: editor-plugin-1 10 | - name: editor-plugin-2 11 | plugin: 12 | kubernetes: 13 | name: editor-plugin-2 14 | plugins: 15 | editor-plugin-1: 16 | kind: DevWorkspaceTemplate 17 | apiVersion: workspace.devfile.io/v1alpha2 18 | metadata: 19 | name: editor-plugin-1 20 | labels: 21 | "devworkspace.devfile.io/editor-name": "test-editor-1" 22 | spec: 23 | components: 24 | - name: editor-container 25 | container: 26 | image: "test-image" 27 | editor-plugin-2: 28 | kind: DevWorkspaceTemplate 29 | apiVersion: workspace.devfile.io/v1alpha2 30 | metadata: 31 | name: plugin-sidecar 32 | labels: 33 | "devworkspace.devfile.io/editor-name": "test-editor-2" 34 | spec: 35 | components: 36 | - name: plugin-container 37 | container: 38 | image: "test-image" 39 | 40 | output: 41 | errRegexp: 'devworkspace defines multiple editors.*' 42 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/disabled/error_plugin-needs-missing-editor.yaml: -------------------------------------------------------------------------------- 1 | name: "Plugin needs missing editor" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: my-plugin 7 | plugin: 8 | kubernetes: 9 | name: my-plugin 10 | plugins: 11 | my-plugin: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-sidecar 16 | labels: 17 | "devworkspace.devfile.io/editor-compatibility": "my-editor" 18 | spec: 19 | components: 20 | - name: plugin-container 21 | container: 22 | image: "test-image" 23 | 24 | output: 25 | errRegexp: 'invalid plugins defined in devworkspace: no editor defined 26 | in workspace but Component\(s\) \[my-plugin\] depend on editor my-editor' 27 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails nicely when no HTTP client provided and id is used" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: test/plugin 9 | 10 | output: 11 | errRegexp: "cannot resolve resources by id: no HTTP client provided" 12 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails nicely when no HTTP client provided and uri is used" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | uri: https://test-repo.io/my-plugino 9 | 10 | output: 11 | errRegexp: "cannot resolve resources by URI: no HTTP client provided" 12 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails nicely when no Kubernetes client provided" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | kubernetes: 9 | name: test-plugin-a 10 | namespace: test-ns 11 | 12 | output: 13 | errRegexp: "cannot resolve resources by kubernetes reference: no kubernetes client provided" 14 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/general/fail-nicely-when-no-namespace.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails nicely when no Kubernetes namespace is provided and there's no default" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | kubernetes: 9 | name: test-plugin-a 10 | 11 | output: 12 | errRegexp: "specifies a kubernetes reference without namespace and a default is not provided" 13 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails nicely when no registry URL is provided and there's no default" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: test/plugin 9 | 10 | output: 11 | errRegexp: "plugin test-plugin does not specify a registryUrl" 12 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/implicit-spec-contributions/error_merged-component-has-apply-command.yml: -------------------------------------------------------------------------------- 1 | name: "Returns error if merged component is target of apply command" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-component 7 | container: 8 | image: test-image 9 | commands: 10 | - id: base-apply 11 | apply: 12 | component: test-component 13 | - id: base-exec 14 | exec: 15 | component: test-component 16 | - id: base-composite 17 | composite: 18 | commands: 19 | - base-apply 20 | - base-exec 21 | contributions: 22 | - name: test-contribution 23 | uri: test-contribution.yaml 24 | 25 | devfileResources: 26 | test-contribution.yaml: 27 | schemaVersion: 2.1.0 28 | metadata: 29 | name: test-contribution 30 | components: 31 | - name: contribution 32 | attributes: 33 | controller.devfile.io/container-contribution: true 34 | container: 35 | image: contribution-image 36 | commands: 37 | - id: test-apply 38 | apply: 39 | component: contribution 40 | 41 | output: 42 | errRegexp: "apply command test-apply uses container contribution contribution as component" -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml: -------------------------------------------------------------------------------- 1 | name: "Attempting to override undefined plugin component" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "bad-override" 7 | plugin: 8 | kubernetes: 9 | name: override 10 | components: 11 | - name: non-existent 12 | container: 13 | memoryLimit: 512Mi 14 | devworkspaceResources: 15 | override: 16 | metadata: 17 | name: override 18 | annotations: 19 | "controller.devfile.io/allow-import-from": "*" 20 | spec: 21 | components: 22 | - name: my-component 23 | container: 24 | image: test-image 25 | 26 | output: 27 | errRegexp: "Some Components do not override any existing element: non-existent.*" 28 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/k8s-ref/error_conflicting-merge.yaml: -------------------------------------------------------------------------------- 1 | name: "Component conflicts with plugin component" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "component-conflict" 7 | plugin: 8 | kubernetes: 9 | name: test-plugin 10 | - name: my-component 11 | container: 12 | image: test-image 13 | devworkspaceResources: 14 | test-plugin: 15 | metadata: 16 | name: test-plugin 17 | annotations: 18 | "controller.devfile.io/allow-import-from": "*" 19 | spec: 20 | components: 21 | - name: my-component 22 | container: 23 | image: test-image 24 | 25 | output: 26 | errRegexp: "Some Components are already defined in plugin '.*': my-component.*" 27 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/k8s-ref/error_error-when-retrieving-plugin.yaml: -------------------------------------------------------------------------------- 1 | name: "Error retrieving plugin" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "bad-plugin" 7 | plugin: 8 | kubernetes: 9 | name: test-plugin 10 | errors: 11 | test-plugin: 12 | message: "Internal k8s error" 13 | 14 | output: 15 | errRegexp: ".*failed to retrieve.*bad-plugin.*Internal k8s error.*" 16 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/k8s-ref/error_plugin-not-found.yaml: -------------------------------------------------------------------------------- 1 | name: "Referenced plugin cannot be found" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "bad-plugin" 7 | plugin: 8 | kubernetes: 9 | name: test-plugin 10 | errors: 11 | test-plugin: 12 | isNotFound: true 13 | message: "Plugin not found" 14 | 15 | output: 16 | errRegexp: "plugin for component bad-plugin not found.*" 17 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/k8s-ref/error_plugin-references-self.yml: -------------------------------------------------------------------------------- 1 | name: "Plugin references self" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | annotations: 17 | "controller.devfile.io/allow-import-from": "*" 18 | spec: 19 | components: 20 | - name: plugin-a 21 | plugin: 22 | kubernetes: 23 | name: plugin-a 24 | namespace: devworkspace-plugins 25 | 26 | output: 27 | errRegexp: "DevWorkspace has an cycle in references.*" 28 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml: -------------------------------------------------------------------------------- 1 | name: "Plugins have reference cycle" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | annotations: 17 | "controller.devfile.io/allow-import-from": "*" 18 | spec: 19 | components: 20 | - name: plugin-b 21 | plugin: 22 | kubernetes: 23 | name: plugin-b 24 | namespace: devworkspace-plugins 25 | plugin-b: 26 | kind: DevWorkspaceTemplate 27 | apiVersion: workspace.devfile.io/v1alpha2 28 | metadata: 29 | name: plugin-b 30 | annotations: 31 | "controller.devfile.io/allow-import-from": "*" 32 | spec: 33 | components: 34 | - name: plugin-a 35 | plugin: 36 | kubernetes: 37 | name: plugin-a 38 | namespace: devworkspace-plugins 39 | 40 | output: 41 | errRegexp: "DevWorkspace has an cycle in references.*" 42 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/namespace-restriction/error_read-dwt-from-non-approved-namespace.yaml: -------------------------------------------------------------------------------- 1 | name: "Try to read a DWT from another namespace and no annotation exists" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | namespace: not-test-namespace 17 | annotations: 18 | controller.devfile.io/allow-import-from: "namespace1,namespace2" 19 | spec: 20 | components: 21 | - name: plugin-a-container 22 | container: 23 | name: test-container 24 | image: test-img 25 | 26 | output: 27 | errRegexp: "plugin for component plugin-a not found" 28 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/namespace-restriction/error_read-plain-dwt-from-another-namespace copy.yaml: -------------------------------------------------------------------------------- 1 | name: "Try to read a DWT from another namespace with empty annotation" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | namespace: not-test-namespace 17 | annotations: 18 | controller.devfile.io/allow-import-from: "" 19 | spec: 20 | components: 21 | - name: plugin-a-container 22 | container: 23 | name: test-container 24 | image: test-img 25 | 26 | output: 27 | errRegexp: "plugin for component plugin-a not found" 28 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/namespace-restriction/error_read-plain-dwt-from-another-namespace.yaml: -------------------------------------------------------------------------------- 1 | name: "Try to read a DWT from another namespace and no annotation exists" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | namespace: not-test-namespace 17 | spec: 18 | components: 19 | - name: plugin-a-container 20 | container: 21 | name: test-container 22 | image: test-img 23 | 24 | output: 25 | errRegexp: "plugin for component plugin-a not found" 26 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/namespace-restriction/read-dwt-in-permitted-namespace.yaml.yaml: -------------------------------------------------------------------------------- 1 | name: "Can read DevWorkspaceTemplate in permitted namespace" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | namespace: not-test-namespace 17 | annotations: 18 | controller.devfile.io/allow-import-from: "namespace1,namespace2,test-namespace" 19 | spec: 20 | components: 21 | - name: plugin-a-container 22 | container: 23 | name: test-container 24 | image: test-img 25 | 26 | output: 27 | devworkspace: 28 | components: 29 | - name: plugin-a-container 30 | attributes: 31 | controller.devfile.io/imported-by: "plugin-a" 32 | container: 33 | name: test-container 34 | image: test-img 35 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/namespace-restriction/read-dwt-in-same-namespace.yaml: -------------------------------------------------------------------------------- 1 | name: "Can read DevWorkspaceTemplate in same namespace as DevWorkspace" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | namespace: test-namespace 17 | spec: 18 | components: 19 | - name: plugin-a-container 20 | container: 21 | name: test-container 22 | image: test-img 23 | 24 | output: 25 | devworkspace: 26 | components: 27 | - name: plugin-a-container 28 | attributes: 29 | controller.devfile.io/imported-by: "plugin-a" 30 | container: 31 | name: test-container 32 | image: test-img 33 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/namespace-restriction/read-dwt-with-any-namespace-annotation.yaml: -------------------------------------------------------------------------------- 1 | name: "Can read DevWorkspaceTemplate when DWT is allowed for all namespaces" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: "plugin-a" 7 | plugin: 8 | kubernetes: 9 | name: plugin-a 10 | devworkspaceResources: 11 | plugin-a: 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: plugin-a 16 | namespace: not-test-namespace 17 | annotations: 18 | controller.devfile.io/allow-import-from: "*" 19 | spec: 20 | components: 21 | - name: plugin-a-container 22 | container: 23 | name: test-container 24 | image: test-img 25 | 26 | output: 27 | devworkspace: 28 | components: 29 | - name: plugin-a-container 30 | attributes: 31 | controller.devfile.io/imported-by: "plugin-a" 32 | container: 33 | name: test-container 34 | image: test-img 35 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/parent/error_parent-has-parent.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails when parent is not flattened (has parent)" 2 | 3 | input: 4 | devworkspace: 5 | parent: 6 | kubernetes: 7 | name: test-parent-k8s 8 | components: 9 | - name: regular-component 10 | container: 11 | image: regular-test-image 12 | name: regular-container 13 | devworkspaceResources: 14 | test-parent-k8s: 15 | kind: DevWorkspaceTemplate 16 | apiVersion: workspace.devfile.io/v1alpha2 17 | metadata: 18 | name: parent-devworkspacetemplate 19 | annotations: 20 | "controller.devfile.io/allow-import-from": "*" 21 | spec: 22 | parent: 23 | id: another-parent 24 | components: 25 | - name: parent-component 26 | container: 27 | image: test-img 28 | env: 29 | - name: test-env 30 | value: original-value 31 | 32 | output: 33 | errRegexp: "parents containing plugins or parents are not supported" 34 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/parent/error_parent-has-plugins.yaml: -------------------------------------------------------------------------------- 1 | name: "Fails when parent is not flattened (has plugin)" 2 | 3 | input: 4 | devworkspace: 5 | parent: 6 | kubernetes: 7 | name: test-parent-k8s 8 | components: 9 | - name: regular-component 10 | container: 11 | image: regular-test-image 12 | name: regular-container 13 | devworkspaceResources: 14 | test-parent-k8s: 15 | kind: DevWorkspaceTemplate 16 | apiVersion: workspace.devfile.io/v1alpha2 17 | metadata: 18 | name: parent-devworkspacetemplate 19 | annotations: 20 | "controller.devfile.io/allow-import-from": "*" 21 | spec: 22 | components: 23 | - name: parent-component 24 | plugin: 25 | id: parent-plugin 26 | 27 | output: 28 | errRegexp: "parents containing plugins or parents are not supported" 29 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace registry contains non-devfile type content" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: "https://test-registry.io/subpath" 10 | devworkspaceResources: 11 | "https://test-registry.io/subpath/devfiles/my/test/plugin": 12 | metadata: 13 | name: test-plugin 14 | spec: 15 | components: 16 | - name: plugin-a 17 | container: 18 | name: test-container 19 | image: test-image 20 | 21 | 22 | output: 23 | errRegexp: "could not find devfile or devworkspace object at 'https://test-registry.io/subpath/devfiles/my/test/plugin'" 24 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/error_invalid-schema-version.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace references plugin with invalid schemaVersion" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: "https://test-registry.io/subpath" 10 | devfileResources: 11 | "https://test-registry.io/subpath/devfiles/my/test/plugin": 12 | schemaVersion: 1.0.0 13 | metadata: 14 | name: "plugin-a" 15 | components: 16 | - name: plugin-a 17 | container: 18 | name: test-container 19 | image: test-image 20 | 21 | output: 22 | errRegexp: "could not process devfile: unsupported schemaVersion '1.0.0'" 23 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/error_on-fetch.yaml: -------------------------------------------------------------------------------- 1 | name: "Error when fetching plugin" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: "https://test-registry.io/subpath" 10 | errors: 11 | "https://test-registry.io/subpath/devfiles/my/test/plugin": 12 | message: "testing error" 13 | 14 | output: 15 | errRegexp: "failed to fetch file from.*testing error" 16 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/error_plugin-not-found.yaml: -------------------------------------------------------------------------------- 1 | name: "Plugin not found in registry" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: "https://test-registry.io/subpath" 10 | errors: 11 | "https://test-registry.io/subpath/devfiles/my/test/plugin": 12 | statusCode: 404 13 | 14 | output: 15 | errRegexp: "could not fetch file from.*got status 404" 16 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/error_unparseable-url.yaml: -------------------------------------------------------------------------------- 1 | name: "Error when parsing registryURL" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: ":/test-registry.io/subpath" 10 | 11 | output: 12 | errRegexp: "failed to parse registry URL for component test-plugin" 13 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace references DevWorkspaceTemplate plugin from registry" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: "https://test-registry.io/subpath" 10 | devworkspaceResources: 11 | "https://test-registry.io/subpath/devfiles/my/test/plugin": 12 | kind: DevWorkspaceTemplate 13 | apiVersion: workspace.devfile.io/v1alpha2 14 | metadata: 15 | name: test-plugin 16 | spec: 17 | components: 18 | - name: plugin-a 19 | container: 20 | name: test-container 21 | image: test-image 22 | 23 | 24 | output: 25 | devworkspace: 26 | components: 27 | - name: plugin-a 28 | attributes: 29 | controller.devfile.io/imported-by: "test-plugin" 30 | container: 31 | name: test-container 32 | image: test-image 33 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace references plugin from plugin registry" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | id: my/test/plugin 9 | registryUrl: "https://test-registry.io/subpath" 10 | devfileResources: 11 | "https://test-registry.io/subpath/devfiles/my/test/plugin": 12 | schemaVersion: 2.0.0 13 | metadata: 14 | name: "plugin-a" 15 | components: 16 | - name: plugin-a 17 | container: 18 | name: test-container 19 | image: test-image 20 | 21 | output: 22 | devworkspace: 23 | components: 24 | - name: plugin-a 25 | attributes: 26 | controller.devfile.io/imported-by: "test-plugin" 27 | container: 28 | name: test-container 29 | image: test-image 30 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace reference URI containing non-devfile type content" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | uri: "https://my-plugin.io/test" 9 | devworkspaceResources: 10 | "https://my-plugin.io/test": 11 | metadata: 12 | name: test-plugin 13 | spec: 14 | components: 15 | - name: plugin-a 16 | container: 17 | name: test-container 18 | image: test-image 19 | 20 | 21 | output: 22 | errRegexp: "could not find devfile or devworkspace object at 'https://my-plugin.io/test'" 23 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace references plugin with invalid schemaVersion" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | uri: https://test-registry.io/old-devfiles 9 | devfileResources: 10 | "https://test-registry.io/old-devfiles": 11 | schemaVersion: 1.0.0 12 | metadata: 13 | name: "plugin-a" 14 | components: 15 | - name: plugin-a 16 | container: 17 | name: test-container 18 | image: test-image 19 | 20 | output: 21 | errRegexp: "could not process devfile: unsupported schemaVersion '1.0.0'" 22 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-uri/error_on-fetch.yaml: -------------------------------------------------------------------------------- 1 | name: "Error when fetching plugin" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | uri: https://test-registry.io/error 9 | errors: 10 | "https://test-registry.io/error": 11 | message: "testing error" 12 | 13 | output: 14 | errRegexp: "failed to fetch file from.*testing error" 15 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/plugin-uri/error_plugin-not-found.yaml: -------------------------------------------------------------------------------- 1 | name: "Plugin not found in at URI" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: test-plugin 7 | plugin: 8 | uri: "https://test-registry.io/notfound" 9 | errors: 10 | "https://test-registry.io/notfound": 11 | statusCode: 404 12 | 13 | output: 14 | errRegexp: "could not fetch file from.*got status 404" 15 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/empty-workspace.yaml: -------------------------------------------------------------------------------- 1 | name: "Workspace that defines no contributions or components" 2 | 3 | input: 4 | devworkspace: {} 5 | 6 | output: 7 | devworkspace: {} -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_bad-plugin-merge.yaml: -------------------------------------------------------------------------------- 1 | name: "Attempting to override undefined plugin component" 2 | 3 | input: 4 | contributions: 5 | - name: "bad-override" 6 | kubernetes: 7 | name: override 8 | components: 9 | - name: non-existent 10 | container: 11 | memoryLimit: 512Mi 12 | devworkspaceResources: 13 | override: 14 | metadata: 15 | name: override 16 | annotations: 17 | "controller.devfile.io/allow-import-from": "*" 18 | spec: 19 | components: 20 | - name: my-component 21 | container: 22 | image: test-image 23 | 24 | output: 25 | errRegexp: "Some Components do not override any existing element: non-existent.*" 26 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_conflicting-merge.yaml: -------------------------------------------------------------------------------- 1 | name: "Component conflicts with plugin component" 2 | 3 | input: 4 | devworkspace: 5 | components: 6 | - name: my-component 7 | container: 8 | image: test-image 9 | contributions: 10 | - name: "component-conflict" 11 | kubernetes: 12 | name: test-plugin 13 | 14 | devworkspaceResources: 15 | test-plugin: 16 | metadata: 17 | name: test-plugin 18 | annotations: 19 | "controller.devfile.io/allow-import-from": "*" 20 | spec: 21 | components: 22 | - name: my-component 23 | container: 24 | image: test-image 25 | 26 | output: 27 | errRegexp: "Some Components are already defined in plugin '.*': my-component.*" 28 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_error-when-retrieving-plugin.yaml: -------------------------------------------------------------------------------- 1 | name: "Error retrieving plugin" 2 | 3 | input: 4 | contributions: 5 | - name: "bad-plugin" 6 | kubernetes: 7 | name: test-plugin 8 | errors: 9 | test-plugin: 10 | message: "Internal k8s error" 11 | 12 | output: 13 | errRegexp: ".*failed to retrieve.*bad-plugin.*Internal k8s error.*" 14 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_fetch-unparseable-file.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace reference URI containing non-devfile type content" 2 | 3 | input: 4 | contributions: 5 | - name: test-plugin 6 | uri: "https://my-plugin.io/test" 7 | devworkspaceResources: 8 | "https://my-plugin.io/test": 9 | metadata: 10 | name: test-plugin 11 | spec: 12 | components: 13 | - name: plugin-a 14 | container: 15 | name: test-container 16 | image: test-image 17 | 18 | 19 | output: 20 | errRegexp: "could not find devfile or devworkspace object at 'https://my-plugin.io/test'" 21 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_invalid-schema-version.yaml: -------------------------------------------------------------------------------- 1 | name: "DevWorkspace references plugin with invalid schemaVersion" 2 | 3 | input: 4 | contributions: 5 | - name: test-plugin 6 | uri: https://test-registry.io/old-devfiles 7 | devfileResources: 8 | "https://test-registry.io/old-devfiles": 9 | schemaVersion: 1.0.0 10 | metadata: 11 | name: "plugin-a" 12 | components: 13 | - name: plugin-a 14 | container: 15 | name: test-container 16 | image: test-image 17 | 18 | output: 19 | errRegexp: "could not process devfile: unsupported schemaVersion '1.0.0'" 20 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_on-fetch.yaml: -------------------------------------------------------------------------------- 1 | name: "Error when fetching plugin" 2 | 3 | input: 4 | contributions: 5 | - name: test-plugin 6 | uri: https://test-registry.io/error 7 | errors: 8 | "https://test-registry.io/error": 9 | message: "testing error" 10 | 11 | output: 12 | errRegexp: "failed to fetch file from.*testing error" 13 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_plugin-not-found-k8s.yaml: -------------------------------------------------------------------------------- 1 | name: "Referenced plugin cannot be found" 2 | 3 | input: 4 | contributions: 5 | - name: "bad-plugin" 6 | kubernetes: 7 | name: test-plugin 8 | errors: 9 | test-plugin: 10 | isNotFound: true 11 | message: "Plugin not found" 12 | 13 | output: 14 | errRegexp: "plugin for component bad-plugin not found.*" 15 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_plugin-not-found-uri.yaml: -------------------------------------------------------------------------------- 1 | name: "Plugin not found in at URI" 2 | 3 | input: 4 | contributions: 5 | - name: test-plugin 6 | uri: "https://test-registry.io/notfound" 7 | errors: 8 | "https://test-registry.io/notfound": 9 | statusCode: 404 10 | 11 | output: 12 | errRegexp: "could not fetch file from.*got status 404" 13 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_plugin-references-self.yml: -------------------------------------------------------------------------------- 1 | name: "Plugin references self" 2 | 3 | input: 4 | contributions: 5 | - name: "plugin-a" 6 | kubernetes: 7 | name: plugin-a 8 | devworkspaceResources: 9 | plugin-a: 10 | kind: DevWorkspaceTemplate 11 | apiVersion: workspace.devfile.io/v1alpha2 12 | metadata: 13 | name: plugin-a 14 | annotations: 15 | "controller.devfile.io/allow-import-from": "*" 16 | spec: 17 | components: 18 | - name: plugin-a 19 | plugin: 20 | kubernetes: 21 | name: plugin-a 22 | namespace: devworkspace-plugins 23 | 24 | output: 25 | errRegexp: "DevWorkspace has an cycle in references.*" 26 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/spec-contributions/error_plugins-have-cycle.yml: -------------------------------------------------------------------------------- 1 | name: "Plugins have reference cycle" 2 | 3 | input: 4 | contributions: 5 | - name: "plugin-a" 6 | kubernetes: 7 | name: plugin-a 8 | devworkspaceResources: 9 | plugin-a: 10 | kind: DevWorkspaceTemplate 11 | apiVersion: workspace.devfile.io/v1alpha2 12 | metadata: 13 | name: plugin-a 14 | annotations: 15 | "controller.devfile.io/allow-import-from": "*" 16 | spec: 17 | components: 18 | - name: plugin-b 19 | plugin: 20 | kubernetes: 21 | name: plugin-b 22 | namespace: devworkspace-plugins 23 | plugin-b: 24 | kind: DevWorkspaceTemplate 25 | apiVersion: workspace.devfile.io/v1alpha2 26 | metadata: 27 | name: plugin-b 28 | annotations: 29 | "controller.devfile.io/allow-import-from": "*" 30 | spec: 31 | components: 32 | - name: plugin-a 33 | plugin: 34 | kubernetes: 35 | name: plugin-a 36 | namespace: devworkspace-plugins 37 | 38 | output: 39 | errRegexp: "DevWorkspace has an cycle in references.*" 40 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/volume_merging/does-nothing-when-no-merge-needed.yaml: -------------------------------------------------------------------------------- 1 | name: "Merges volume components from parent and plugins" 2 | 3 | input: 4 | devworkspace: 5 | parent: 6 | uri: "parent" 7 | components: 8 | - name: normal-workspace-volume 9 | volume: {} 10 | - name: "plugin-1" 11 | plugin: 12 | uri: "plugin-1" 13 | devfileResources: 14 | "parent": 15 | schemaVersion: 2.0.0 16 | metadata: 17 | name: "parent" 18 | components: 19 | - name: new-parent-volume 20 | volume: {} 21 | "plugin-1": 22 | schemaVersion: 2.0.0 23 | metadata: 24 | name: "plugin-1" 25 | components: 26 | - name: new-plugin-1-volume 27 | volume: {} 28 | 29 | output: 30 | devworkspace: 31 | components: 32 | - name: normal-workspace-volume 33 | volume: {} 34 | - name: new-parent-volume 35 | attributes: 36 | "controller.devfile.io/imported-by": parent 37 | volume: {} 38 | - name: new-plugin-1-volume 39 | attributes: 40 | "controller.devfile.io/imported-by": plugin-1 41 | volume: {} 42 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/volume_merging/error-invalid-size-in-merged-volume.yaml: -------------------------------------------------------------------------------- 1 | name: "Error when a to-be-merged volume has invalid size" 2 | 3 | input: 4 | devworkspace: 5 | parent: 6 | uri: "parent" 7 | components: 8 | - name: duplicated-volume 9 | volume: {} 10 | 11 | devfileResources: 12 | "parent": 13 | schemaVersion: 2.0.0 14 | metadata: 15 | name: "parent" 16 | components: 17 | - name: duplicated-volume 18 | volume: 19 | size: "invalid" 20 | 21 | output: 22 | errRegexp: "failed to merge DevWorkspace volumes: quantities.*" 23 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/volume_merging/makes-merged-volume-persistent-if-needed.yaml: -------------------------------------------------------------------------------- 1 | name: "Makes merged volume persistent if duplicate is persistent" 2 | 3 | input: 4 | devworkspace: 5 | parent: 6 | uri: "parent" 7 | components: 8 | - name: duplicated-volume 9 | volume: 10 | emphemeral: true 11 | - name: "plugin-1" 12 | plugin: 13 | uri: "plugin-1" 14 | 15 | devfileResources: 16 | "parent": 17 | schemaVersion: 2.0.0 18 | metadata: 19 | name: "parent" 20 | components: 21 | - name: duplicated-volume 22 | volume: 23 | emphemeral: true 24 | "plugin-1": 25 | schemaVersion: 2.0.0 26 | metadata: 27 | name: "plugin-1" 28 | components: 29 | - name: duplicated-volume 30 | volume: {} 31 | 32 | output: 33 | devworkspace: 34 | components: 35 | - name: duplicated-volume 36 | volume: {} 37 | -------------------------------------------------------------------------------- /pkg/library/flatten/testdata/volume_merging/makes-merged-volume-use-largest-size.yaml: -------------------------------------------------------------------------------- 1 | name: "Makes merged volume persistent use largest size from duplicates" 2 | 3 | input: 4 | devworkspace: 5 | parent: 6 | uri: "parent" 7 | components: 8 | - name: duplicated-volume 9 | volume: {} 10 | - name: "plugin-1" 11 | plugin: 12 | uri: "plugin-1" 13 | 14 | devfileResources: 15 | "parent": 16 | schemaVersion: 2.0.0 17 | metadata: 18 | name: "parent" 19 | components: 20 | - name: duplicated-volume 21 | volume: 22 | size: 2Gi 23 | "plugin-1": 24 | schemaVersion: 2.0.0 25 | metadata: 26 | name: "plugin-1" 27 | components: 28 | - name: duplicated-volume 29 | volume: 30 | size: 1Gi 31 | 32 | output: 33 | devworkspace: 34 | components: 35 | - name: duplicated-volume 36 | volume: 37 | size: 2Gi 38 | -------------------------------------------------------------------------------- /pkg/library/home/testdata/persistent-home/creates-home-vm-when-enabled.yaml: -------------------------------------------------------------------------------- 1 | name: "Creates persistent home volume when persistUserHome is enabled" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | config: 6 | workspace: 7 | persistUserHome: 8 | enabled: true 9 | disableInitContainer: true 10 | workspace: 11 | components: 12 | - name: testing-container-1 13 | container: 14 | image: testing-image-1 15 | volumeMounts: 16 | - name: my-defined-volume 17 | path: /my-defined-volume-path 18 | - name: my-defined-volume 19 | volume: {} 20 | 21 | output: 22 | workspace: 23 | components: 24 | - name: testing-container-1 25 | container: 26 | image: testing-image-1 27 | volumeMounts: 28 | - name: my-defined-volume 29 | path: /my-defined-volume-path 30 | - name: persistent-home 31 | path: /home/user/ 32 | - name: my-defined-volume 33 | volume: {} 34 | - name: persistent-home 35 | volume: {} 36 | -------------------------------------------------------------------------------- /pkg/library/home/testdata/persistent-home/no-home-vm-when-disabled.yaml: -------------------------------------------------------------------------------- 1 | name: "Does not create persistent home volume when persistUserHome is disabled" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | config: 6 | workspace: 7 | persistUserHome: 8 | enabled: false 9 | workspace: 10 | components: 11 | - name: testing-container-1 12 | container: 13 | image: testing-image-1 14 | volumeMounts: 15 | - name: my-defined-volume 16 | path: /my-defined-volume-path 17 | - name: my-defined-volume 18 | volume: {} 19 | 20 | output: 21 | workspace: 22 | components: 23 | - name: testing-container-1 24 | container: 25 | image: testing-image-1 26 | volumeMounts: 27 | - name: my-defined-volume 28 | path: /my-defined-volume-path 29 | - name: my-defined-volume 30 | volume: {} 31 | -------------------------------------------------------------------------------- /pkg/library/home/testdata/persistent-home/noop-if-init-command-already-defined.yaml: -------------------------------------------------------------------------------- 1 | name: "Does not create persistent home volume if command with an id of 'init-persistent-home' is already defined" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | config: 6 | workspace: 7 | persistUserHome: 8 | enabled: true 9 | workspace: 10 | components: 11 | - name: testing-container-1 12 | container: 13 | image: testing-image-1 14 | commands: 15 | - id: init-persistent-home 16 | apply: 17 | component: testing-container-1 18 | 19 | output: 20 | error: "failed to add init container for home persistence setup: command with id init-persistent-home already exists in the devworkspace" 21 | workspace: 22 | components: 23 | - name: testing-container-1 24 | container: 25 | image: testing-image-1 26 | commands: 27 | - id: init-persistent-home 28 | apply: 29 | component: testing-container-1 30 | -------------------------------------------------------------------------------- /pkg/library/home/testdata/persistent-home/noop-if-init-prestartevent-already-defined.yaml: -------------------------------------------------------------------------------- 1 | name: "Does not create persistent home volume if prestart event with an id of 'init-persistent-home' is already defined" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | config: 6 | workspace: 7 | persistUserHome: 8 | enabled: true 9 | workspace: 10 | events: 11 | prestart: 12 | - init-persistent-home 13 | 14 | output: 15 | error: "failed to add init container for home persistence setup: command with id init-persistent-home already exists in the devworkspace" 16 | workspace: 17 | events: 18 | prestart: 19 | - init-persistent-home 20 | -------------------------------------------------------------------------------- /pkg/library/home/testdata/persistent-home/noop-if-no-components.yaml: -------------------------------------------------------------------------------- 1 | name: "No op if there are no components" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | config: 6 | workspace: 7 | persistUserHome: 8 | enabled: true 9 | workspace: 10 | components: [] 11 | 12 | output: 13 | workspace: 14 | components: [] 15 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/k8s_objects/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-configmap 5 | data: 6 | key: value 7 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/k8s_objects/kubernetes-list.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: List 3 | metadata: 4 | resourceVersion: "" 5 | items: 6 | - apiVersion: v1 7 | kind: Pod 8 | metadata: 9 | name: test-pod 10 | labels: 11 | testLabel: testPod 12 | spec: 13 | containers: 14 | - name: test-container 15 | image: test-image 16 | resources: 17 | limits: 18 | memory: "128Mi" 19 | cpu: "500m" 20 | ports: 21 | - containerPort: 8080 22 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/k8s_objects/non-k8s-object.yaml: -------------------------------------------------------------------------------- 1 | random: ymal 2 | that: does 3 | not: 4 | satisfy: 5 | - typeMeta 6 | - objectMeta 7 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/k8s_objects/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: test-pod 5 | labels: 6 | testLabel: testPod 7 | spec: 8 | containers: 9 | - name: test-container 10 | image: test-image 11 | resources: 12 | limits: 13 | memory: "128Mi" 14 | cpu: "500m" 15 | ports: 16 | - containerPort: 8080 17 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/k8s_objects/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: test-service 5 | spec: 6 | selector: 7 | test: test-app 8 | ports: 9 | - port: 8080 10 | targetPort: 8081 11 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/k8s_objects/unrecognized-kind.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Plod 3 | metadata: 4 | name: test-pod 5 | labels: 6 | testLabel: testPod 7 | spec: 8 | containers: 9 | - name: test-container 10 | image: test-image 11 | resources: 12 | limits: 13 | memory: "128Mi" 14 | cpu: "500m" 15 | ports: 16 | - containerPort: 8080 17 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/provision_tests/error-if-object-not-inlined.yaml: -------------------------------------------------------------------------------- 1 | name: "Error if Kuberenetes component is not inlined" 2 | 3 | input: 4 | components: 5 | - name: "container-component" 6 | container: 7 | image: "test-image" 8 | - name: "test-pod" 9 | kubernetes: 10 | deployByDefault: true 11 | uri: test-uri 12 | - name: "test-service" 13 | openshift: 14 | deployByDefault: true 15 | inlined: | 16 | apiVersion: v1 17 | kind: Service 18 | metadata: 19 | name: test-service 20 | spec: 21 | selector: 22 | test: test-app 23 | ports: 24 | - port: 8080 25 | targetPort: 8081 26 | 27 | output: 28 | errRegexp: "Ignored components that use unsupported features" 29 | -------------------------------------------------------------------------------- /pkg/library/kubernetes/testdata/provision_tests/no-k8s-components-devworkspace.yaml: -------------------------------------------------------------------------------- 1 | name: "Does nothing if DevWorkspace defines no Kubernetes/OpenShift components" 2 | 3 | input: 4 | components: 5 | - name: "container-component" 6 | container: 7 | image: "test-image" 8 | 9 | output: {} 10 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/common.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package lifecycle 17 | 18 | func listContains(query string, list []string) bool { 19 | for _, elem := range list { 20 | if query == elem { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/basic_postStart.yaml: -------------------------------------------------------------------------------- 1 | name: "Should add postStart lifecycle hook for basic event" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-postStart 7 | exec: 8 | component: test-component 9 | commandLine: "echo 'hello world'" 10 | events: 11 | postStart: 12 | - test-postStart 13 | containers: 14 | - name: test-component 15 | image: test-img 16 | 17 | output: 18 | containers: 19 | - name: test-component 20 | image: test-img 21 | lifecycle: 22 | postStart: 23 | exec: 24 | command: 25 | - "/bin/sh" 26 | - "-c" 27 | - | 28 | { 29 | echo 'hello world' 30 | } 1>/tmp/poststart-stdout.txt 2>/tmp/poststart-stderr.txt 31 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/error_command_has_env.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when postStart command requires env vars" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-cmd 7 | exec: 8 | component: test-component 9 | commandLine: "echo hello world ${MY_ENV}" 10 | env: 11 | - name: MY_ENV 12 | value: /projects 13 | events: 14 | postStart: 15 | - test-cmd 16 | containers: 17 | - name: test-component 18 | image: test-img 19 | 20 | output: 21 | errRegexp: ".*env vars in postStart command test-cmd are unsupported.*" 22 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/error_postStart_cmd_is_not_exec.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when postStart command is not exec-type" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-command 7 | apply: 8 | component: my-component 9 | events: 10 | postStart: 11 | - test-command 12 | 13 | output: 14 | errRegexp: "can not use Apply-type command in postStart lifecycle event" 15 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/error_postStart_command_does_not_exist.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when postStart command does not exist" 2 | 3 | input: 4 | devfile: 5 | events: 6 | postStart: 7 | - test-cmd 8 | 9 | output: 10 | errRegexp: ".*could not resolve command for postStart event 'test-cmd'.*" 11 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/error_postStart_command_uses_nonexistent_container.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when postStart command requires nonexistent container" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-cmd 7 | exec: 8 | component: test-component-wrong-name 9 | commandLine: "echo hello world" 10 | events: 11 | postStart: 12 | - test-cmd 13 | containers: 14 | - name: test-component 15 | image: test-img 16 | 17 | output: 18 | errRegexp: ".*failed to process postStart event test-cmd:.*container component with name .* not found.*" 19 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/multiple_poststart_commands.yaml: -------------------------------------------------------------------------------- 1 | name: "Multiple postStart commands use same component" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-cmd-1 7 | exec: 8 | component: test-component 9 | commandLine: "echo 'hello world 1'" 10 | - id: test-cmd-2 11 | exec: 12 | component: test-component 13 | commandLine: "echo 'hello world 2'" 14 | events: 15 | postStart: 16 | - test-cmd-1 17 | - test-cmd-2 18 | containers: 19 | - name: test-component 20 | image: test-img 21 | 22 | output: 23 | containers: 24 | - name: test-component 25 | image: test-img 26 | lifecycle: 27 | postStart: 28 | exec: 29 | command: 30 | - "/bin/sh" 31 | - "-c" 32 | - | 33 | { 34 | echo 'hello world 1' 35 | echo 'hello world 2' 36 | } 1>/tmp/poststart-stdout.txt 2>/tmp/poststart-stderr.txt 37 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/no_events.yaml: -------------------------------------------------------------------------------- 1 | name: "Should do nothing when devfile does not specify events" 2 | 3 | input: 4 | devfile: {} 5 | containers: 6 | - name: test-container 7 | image: my-test-image 8 | 9 | output: 10 | containers: 11 | - name: test-container 12 | image: my-test-image 13 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/no_postStart.yaml: -------------------------------------------------------------------------------- 1 | name: "Should do nothing when devfile does not include postStart events" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: prestart-cmd 7 | exec: 8 | component: test-component 9 | commandLine: echo "Hello from $(pwd)" 10 | events: 11 | preStart: 12 | - prestart-cmd 13 | containers: 14 | - name: test-container 15 | image: my-test-image 16 | 17 | output: 18 | containers: 19 | - name: test-container 20 | image: my-test-image 21 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/postStart/workingDir_postStart.yaml: -------------------------------------------------------------------------------- 1 | name: "Should add postStart lifecycle hook for event with workingDir" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-postStart 7 | exec: 8 | component: test-component 9 | commandLine: "echo 'hello world'" 10 | workingDir: "/tmp/test-dir" 11 | events: 12 | postStart: 13 | - test-postStart 14 | containers: 15 | - name: test-component 16 | image: test-img 17 | 18 | output: 19 | containers: 20 | - name: test-component 21 | image: test-img 22 | lifecycle: 23 | postStart: 24 | exec: 25 | command: 26 | - "/bin/sh" 27 | - "-c" 28 | - | 29 | { 30 | cd /tmp/test-dir 31 | echo 'hello world' 32 | } 1>/tmp/poststart-stdout.txt 2>/tmp/poststart-stderr.txt 33 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/basic_preStop.yaml: -------------------------------------------------------------------------------- 1 | name: "Should add preStop lifecycle hook for basic event" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-preStop 7 | exec: 8 | component: test-component 9 | commandLine: "echo 'hello world'" 10 | events: 11 | preStop: 12 | - test-preStop 13 | containers: 14 | - name: test-component 15 | image: test-img 16 | 17 | output: 18 | containers: 19 | - name: test-component 20 | image: test-img 21 | lifecycle: 22 | preStop: 23 | exec: 24 | command: 25 | - "/bin/sh" 26 | - "-c" 27 | - | 28 | { 29 | echo 'hello world' 30 | } 31 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/error_command_has_env.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when preStop command requires env vars" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-cmd 7 | exec: 8 | component: test-component 9 | commandLine: "echo hello world ${MY_ENV}" 10 | env: 11 | - name: MY_ENV 12 | value: /projects 13 | events: 14 | preStop: 15 | - test-cmd 16 | containers: 17 | - name: test-component 18 | image: test-img 19 | 20 | output: 21 | errRegexp: ".*env vars in preStop command test-cmd are unsupported.*" 22 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/error_preStop_cmd_is_not_exec.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when preStop command is not exec-type" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-command 7 | apply: 8 | component: my-component 9 | events: 10 | preStop: 11 | - test-command 12 | 13 | output: 14 | errRegexp: "can not use Apply-type command in preStop lifecycle event" 15 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/error_preStop_command_does_not_exist.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when preStop command does not exist" 2 | 3 | input: 4 | devfile: 5 | events: 6 | preStop: 7 | - test-cmd 8 | 9 | output: 10 | errRegexp: ".*could not resolve command for preStop event 'test-cmd'.*" 11 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/error_preStop_command_uses_nonexistent_container.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when preStop command requires nonexistent container" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-cmd 7 | exec: 8 | component: test-component-wrong-name 9 | commandLine: "echo hello world" 10 | events: 11 | preStop: 12 | - test-cmd 13 | containers: 14 | - name: test-component 15 | image: test-img 16 | 17 | output: 18 | errRegexp: ".*failed to process preStop event 'test-cmd':.*container component with name .* not found.*" 19 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/multiple_prestop_commands.yaml: -------------------------------------------------------------------------------- 1 | name: "Multiple preStop commands use same component" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-cmd-1 7 | exec: 8 | component: test-component 9 | commandLine: "echo 'hello world 1'" 10 | - id: test-cmd-2 11 | exec: 12 | component: test-component 13 | commandLine: "echo 'hello world 2'" 14 | events: 15 | preStop: 16 | - test-cmd-1 17 | - test-cmd-2 18 | containers: 19 | - name: test-component 20 | image: test-img 21 | 22 | output: 23 | containers: 24 | - name: test-component 25 | image: test-img 26 | lifecycle: 27 | preStop: 28 | exec: 29 | command: 30 | - "/bin/sh" 31 | - "-c" 32 | - | 33 | { 34 | echo 'hello world 1' 35 | echo 'hello world 2' 36 | } 37 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/no_events.yaml: -------------------------------------------------------------------------------- 1 | name: "Should do nothing when devfile does not specify events" 2 | 3 | input: 4 | devfile: {} 5 | containers: 6 | - name: test-container 7 | image: my-test-image 8 | 9 | output: 10 | containers: 11 | - name: test-container 12 | image: my-test-image 13 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/no_preStop.yaml: -------------------------------------------------------------------------------- 1 | name: "Should do nothing when devfile does not include preStop events" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: prestart-cmd 7 | exec: 8 | component: test-component 9 | commandLine: echo "Hello from $(pwd)" 10 | events: 11 | preStart: 12 | - prestart-cmd 13 | containers: 14 | - name: test-container 15 | image: my-test-image 16 | 17 | output: 18 | containers: 19 | - name: test-container 20 | image: my-test-image 21 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/preStop/workingDir_preStop.yaml: -------------------------------------------------------------------------------- 1 | name: "Should add preStop lifecycle hook for event with workingDir" 2 | 3 | input: 4 | devfile: 5 | commands: 6 | - id: test-preStop 7 | exec: 8 | component: test-component 9 | commandLine: "echo 'hello world'" 10 | workingDir: "/tmp/test-dir" 11 | events: 12 | preStop: 13 | - test-preStop 14 | containers: 15 | - name: test-component 16 | image: test-img 17 | 18 | output: 19 | containers: 20 | - name: test-component 21 | image: test-img 22 | lifecycle: 23 | preStop: 24 | exec: 25 | command: 26 | - "/bin/sh" 27 | - "-c" 28 | - | 29 | { 30 | cd /tmp/test-dir 31 | echo 'hello world' 32 | } 33 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/prestart/init_and_main_container.yaml: -------------------------------------------------------------------------------- 1 | name: "Should use container as both init and main when multiple commands apply" 2 | 3 | input: 4 | components: 5 | - name: test-container1 6 | container: 7 | image: my-image 8 | - name: test-container2 9 | container: 10 | image: my-image 11 | commands: 12 | - id: test_preStart_command 13 | apply: 14 | component: test-container1 15 | - id: test_regular_command 16 | exec: 17 | component: test-container1 18 | command: "test_command" 19 | events: 20 | preStart: 21 | - "test_preStart_command" 22 | 23 | output: 24 | initContainers: 25 | - name: test-container1 26 | container: 27 | image: my-image 28 | mainContainers: 29 | - name: test-container1 30 | container: 31 | image: my-image 32 | - name: test-container2 33 | container: 34 | image: my-image 35 | errRegexp: 36 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/prestart/no_events.yaml: -------------------------------------------------------------------------------- 1 | name: "Should return all components when devfile contains no events" 2 | 3 | input: 4 | components: 5 | - name: test-container1 6 | container: 7 | image: my-image 8 | - name: test-container2 9 | container: 10 | image: my-image 11 | commands: 12 | - exec: 13 | component: test-container1 14 | command: "test_command" 15 | 16 | output: 17 | initContainers: 18 | mainContainers: 19 | - name: test-container1 20 | container: 21 | image: my-image 22 | - name: test-container2 23 | container: 24 | image: my-image 25 | errRegexp: 26 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/prestart/persistent_home_initcontainer_only_initcontainer.yaml: -------------------------------------------------------------------------------- 1 | name: "Should set the init-persistent-home init container when it is the only init container" 2 | 3 | input: 4 | components: 5 | - name: test-container1 6 | container: 7 | image: my-image 8 | - name: test-container2 9 | container: 10 | image: my-image 11 | - name: init-persistent-home 12 | container: 13 | image: my-image 14 | commands: 15 | - id: test_regular_command 16 | exec: 17 | component: test-container1 18 | command: "test_command" 19 | - id: init-persistent-home 20 | apply: 21 | component: init-persistent-home 22 | events: 23 | preStart: 24 | - "init-persistent-home" 25 | 26 | output: 27 | initContainers: 28 | - name: init-persistent-home 29 | container: 30 | image: my-image 31 | mainContainers: 32 | - name: test-container1 33 | container: 34 | image: my-image 35 | - name: test-container2 36 | container: 37 | image: my-image 38 | errRegexp: 39 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/prestart/prestart_apply_command.yaml: -------------------------------------------------------------------------------- 1 | name: "Should return init container with prestart apply command" 2 | 3 | input: 4 | components: 5 | - name: test-container1 6 | container: 7 | image: my-image 8 | - name: test-container2 9 | container: 10 | image: my-image 11 | commands: 12 | - id: test_apply_command 13 | apply: 14 | component: test-container1 15 | events: 16 | preStart: 17 | - "test_apply_command" 18 | 19 | output: 20 | initContainers: 21 | - name: test-container1 22 | container: 23 | image: my-image 24 | mainContainers: 25 | - name: test-container2 26 | container: 27 | image: my-image 28 | errRegexp: 29 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/testdata/prestart/prestart_exec_command.yaml: -------------------------------------------------------------------------------- 1 | name: "Should return init container with prestart exec command" 2 | 3 | input: 4 | components: 5 | - name: test-container1 6 | container: 7 | image: my-image 8 | - name: test-container2 9 | container: 10 | image: my-image 11 | commands: 12 | - id: test_command 13 | exec: 14 | component: test-container1 15 | command: "test_command" 16 | events: 17 | preStart: 18 | - "test_command" 19 | 20 | output: 21 | errRegexp: "only apply-type commands are supported in the prestart lifecycle binding" 22 | -------------------------------------------------------------------------------- /pkg/library/lifecycle/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2025 Red Hat, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package lifecycle 15 | 16 | import ( 17 | "fmt" 18 | 19 | corev1 "k8s.io/api/core/v1" 20 | ) 21 | 22 | func getContainerWithName(name string, containers []corev1.Container) (*corev1.Container, error) { 23 | for idx, container := range containers { 24 | if container.Name == name { 25 | return &containers[idx], nil 26 | } 27 | } 28 | return nil, fmt.Errorf("container component with name %s not found", name) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/container-cannot-set-restricted-fields.yaml: -------------------------------------------------------------------------------- 1 | name: "Container overrides cannot override container component fields" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | image: override-image 9 | command: ["test"] 10 | args: ["test"] 11 | ports: 12 | - name: test-port 13 | containerPort: 9999 14 | volumeMounts: 15 | - name: test-volume 16 | mountPath: test-mountPath 17 | env: 18 | - name: test_env 19 | value: test_val 20 | container: 21 | image: test-image 22 | container: 23 | name: test-component 24 | image: test-image 25 | command: ["original"] 26 | args: ["original"] 27 | ports: 28 | - name: original-port 29 | containerPort: 8080 30 | volumeMounts: 31 | - name: original-volume 32 | mountPath: original-mountPath 33 | env: 34 | - name: original_env 35 | value: original_val 36 | 37 | 38 | output: 39 | errRegexp: "cannot use container-overrides to override container env" 40 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/container-defines-overrides-json.yaml: -------------------------------------------------------------------------------- 1 | name: "Applies overrides from container-overrides attribute as json" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: {"resources":{"limits":{"nvidia.com/gpu":"1"}}} 8 | container: 9 | image: test-image 10 | container: 11 | name: test-component 12 | image: test-image 13 | 14 | output: 15 | container: 16 | name: test-component 17 | image: test-image 18 | resources: 19 | limits: 20 | nvidia.com/gpu: "1" 21 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/container-defines-overrides.yaml: -------------------------------------------------------------------------------- 1 | name: "Applies overrides from container-overrides attribute" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | resources: 9 | limits: 10 | nvidia.com/gpu: "1" 11 | requests: 12 | nvidia.com/gpu: "1" 13 | securityContext: 14 | runAsUser: 1000 15 | runAsGroup: 3000 16 | fsGroup: 2000 17 | container: 18 | image: test-image 19 | container: 20 | name: test-component 21 | image: test-image 22 | 23 | output: 24 | container: 25 | name: test-component 26 | image: test-image 27 | resources: 28 | limits: 29 | nvidia.com/gpu: "1" 30 | requests: 31 | nvidia.com/gpu: "1" 32 | securityContext: 33 | runAsUser: 1000 34 | runAsGroup: 3000 35 | fsGroup: 2000 36 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/container-overridden-resources-merge.yaml: -------------------------------------------------------------------------------- 1 | name: "Resources from overrides are merged with container-defined resources" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | resources: 9 | limits: 10 | nvidia.com/gpu: "1" 11 | requests: 12 | nvidia.com/gpu: "1" 13 | container: 14 | image: test-image 15 | memoryLimit: 1Gi 16 | memoryRequest: 256Mi 17 | cpuLimit: 1000m 18 | cpuRequest: 500m 19 | container: 20 | name: test-component 21 | image: test-image 22 | resources: 23 | limits: 24 | memory: 1Gi 25 | cpu: 1000m 26 | requests: 27 | memory: 256Mi 28 | cpu: 500m 29 | 30 | output: 31 | container: 32 | name: test-component 33 | image: test-image 34 | resources: 35 | limits: 36 | nvidia.com/gpu: "1" 37 | memory: 1Gi 38 | cpu: 1000m 39 | requests: 40 | nvidia.com/gpu: "1" 41 | memory: 256Mi 42 | cpu: 500m 43 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/error_cannot-parse-override.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns an error when container-override attribute cannot be parsed" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 123 8 | container: 9 | image: test-image 10 | container: 11 | name: test-component 12 | image: test-image 13 | 14 | output: 15 | errRegexp: "failed to parse .* attribute on component test-component.*" 16 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/overrides-can-add-volumemount.yaml: -------------------------------------------------------------------------------- 1 | name: "Container overrides can add volumeMounts" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | volumeMounts: 9 | - name: test-volume 10 | mountPath: /test-volume/path 11 | container: 12 | image: test-image 13 | container: 14 | name: test-component 15 | image: test-image 16 | volumeMounts: 17 | - name: my-volume 18 | mountPath: /my-volume/path 19 | 20 | output: 21 | container: 22 | name: test-component 23 | image: test-image 24 | volumeMounts: 25 | - name: test-volume 26 | mountPath: /test-volume/path 27 | - name: my-volume 28 | mountPath: /my-volume/path 29 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/overrides-can-define-volumemount.yaml: -------------------------------------------------------------------------------- 1 | name: "Container overrides can define a volumeMount" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | volumeMounts: 9 | - name: test-volume 10 | mountPath: /test/path 11 | container: 12 | image: test-image 13 | container: 14 | name: test-component 15 | image: test-image 16 | 17 | output: 18 | container: 19 | name: test-component 20 | image: test-image 21 | volumeMounts: 22 | - name: test-volume 23 | mountPath: /test/path 24 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/overrides-can-override-securityContext.yaml: -------------------------------------------------------------------------------- 1 | name: "container overrides can override securityContext" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | securityContext: 9 | runAsUser: 1001 10 | container: 11 | image: test-image 12 | 13 | container: 14 | name: test-component 15 | image: test-image 16 | securityContext: 17 | runAsUser: 1000 18 | runAsGroup: 2000 19 | 20 | output: 21 | container: 22 | name: test-component 23 | image: test-image 24 | securityContext: 25 | runAsUser: 1001 26 | runAsGroup: 2000 27 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/overrides-can-override-volumemount.yaml: -------------------------------------------------------------------------------- 1 | name: "Container overrides can override existing volumeMounts" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | volumeMounts: 9 | # patchMergeKey is mountPath 10 | - mountPath: /my-volume/path 11 | subPath: test-subpath 12 | container: 13 | image: test-image 14 | container: 15 | name: test-component 16 | image: test-image 17 | volumeMounts: 18 | - name: my-volume 19 | mountPath: /my-volume/path 20 | 21 | output: 22 | container: 23 | name: test-component 24 | image: test-image 25 | volumeMounts: 26 | - name: my-volume 27 | mountPath: /my-volume/path 28 | subPath: test-subpath 29 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/overrides-can-use-delete-directive.yaml: -------------------------------------------------------------------------------- 1 | name: "container overrides can use $patch: delete to delete fields rather than merging" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | securityContext: 9 | $patch: delete 10 | container: 11 | image: test-image 12 | 13 | container: 14 | name: test-component 15 | image: test-image 16 | securityContext: 17 | runAsUser: 1000 18 | runAsGroup: 2000 19 | 20 | output: 21 | container: 22 | name: test-component 23 | image: test-image 24 | securityContext: {} 25 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/container-overrides/overrides-can-use-replace-directive.yaml: -------------------------------------------------------------------------------- 1 | name: "container overrides can use $patch: replace to overwrite fields rather than merging" 2 | 3 | input: 4 | component: 5 | name: test-component 6 | attributes: 7 | container-overrides: 8 | securityContext: 9 | runAsUser: 1001 10 | $patch: replace 11 | container: 12 | image: test-image 13 | 14 | container: 15 | name: test-component 16 | image: test-image 17 | securityContext: 18 | runAsUser: 1000 19 | runAsGroup: 2000 20 | 21 | output: 22 | container: 23 | name: test-component 24 | image: test-image 25 | securityContext: 26 | runAsUser: 1001 27 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/error_cannot-parse-component-attribute.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when cannot parse component attribute" 2 | 3 | input: 4 | workspace: 5 | components: 6 | - name: test-component 7 | attributes: 8 | pod-overrides: 123 9 | container: 10 | image: test-image 11 | 12 | podTemplateSpec: 13 | metadata: 14 | labels: 15 | controller.devfile.io/devworkspace-id: test-id 16 | spec: 17 | containers: 18 | - name: test-component 19 | image: test-image 20 | 21 | output: 22 | errRegexp: "failed to parse pod-overrides attribute on component test-component: .*" -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/error_cannot-parse-global-attribute.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when cannot parse global attribute" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 123 7 | components: 8 | - name: test-component 9 | container: 10 | image: test-image 11 | 12 | podTemplateSpec: 13 | metadata: 14 | labels: 15 | controller.devfile.io/devworkspace-id: test-id 16 | spec: 17 | containers: 18 | - name: test-component 19 | image: test-image 20 | 21 | output: 22 | errRegexp: "failed to parse pod-overrides attribute for workspace: .*" -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/overrides-can-add-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: "Pod overrides can add volumes" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | spec: 8 | volumes: 9 | - name: test-volume-1 10 | components: 11 | - name: test-component 12 | container: 13 | image: test-image 14 | 15 | podTemplateSpec: 16 | metadata: 17 | labels: 18 | controller.devfile.io/devworkspace-id: test-id 19 | spec: 20 | containers: 21 | - name: test-component 22 | image: test-image 23 | volumes: 24 | - name: test-volume-2 25 | 26 | output: 27 | podTemplateSpec: 28 | metadata: 29 | labels: 30 | controller.devfile.io/devworkspace-id: test-id 31 | spec: 32 | containers: 33 | - name: test-component 34 | image: test-image 35 | volumes: 36 | - name: test-volume-1 37 | - name: test-volume-2 38 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/overrides-can-define-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: "Pod overrides can define volumes" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | spec: 8 | volumes: 9 | - name: test-volume 10 | components: 11 | - name: test-component 12 | container: 13 | image: test-image 14 | 15 | podTemplateSpec: 16 | metadata: 17 | labels: 18 | controller.devfile.io/devworkspace-id: test-id 19 | spec: 20 | containers: 21 | - name: test-component 22 | image: test-image 23 | 24 | output: 25 | podTemplateSpec: 26 | metadata: 27 | labels: 28 | controller.devfile.io/devworkspace-id: test-id 29 | spec: 30 | containers: 31 | - name: test-component 32 | image: test-image 33 | volumes: 34 | - name: test-volume 35 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/overrides-can-override-securityContext.yaml: -------------------------------------------------------------------------------- 1 | name: "Pod overrides can override securityContext" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | spec: 8 | securityContext: 9 | runAsUser: 1001 10 | components: 11 | - name: test-component 12 | container: 13 | image: test-image 14 | 15 | podTemplateSpec: 16 | metadata: 17 | labels: 18 | controller.devfile.io/devworkspace-id: test-id 19 | spec: 20 | containers: 21 | - name: test-component 22 | image: test-image 23 | securityContext: 24 | fsGroup: 2000 25 | runAsGroup: 3000 26 | runAsUser: 1000 27 | 28 | 29 | output: 30 | podTemplateSpec: 31 | metadata: 32 | labels: 33 | controller.devfile.io/devworkspace-id: test-id 34 | spec: 35 | containers: 36 | - name: test-component 37 | image: test-image 38 | securityContext: 39 | fsGroup: 2000 40 | runAsGroup: 3000 41 | runAsUser: 1001 42 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/overrides-can-use-delete-directive.yaml: -------------------------------------------------------------------------------- 1 | name: "Pod overrides can use $patch: delete to delete fields rather than merging" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | spec: 8 | securityContext: 9 | $patch: delete 10 | components: 11 | - name: test-component 12 | container: 13 | image: test-image 14 | 15 | podTemplateSpec: 16 | metadata: 17 | labels: 18 | controller.devfile.io/devworkspace-id: test-id 19 | spec: 20 | containers: 21 | - name: test-component 22 | image: test-image 23 | securityContext: 24 | fsGroup: 2000 25 | runAsGroup: 3000 26 | runAsUser: 1000 27 | 28 | 29 | output: 30 | podTemplateSpec: 31 | metadata: 32 | labels: 33 | controller.devfile.io/devworkspace-id: test-id 34 | spec: 35 | containers: 36 | - name: test-component 37 | image: test-image 38 | securityContext: {} 39 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/overrides-can-use-replace-directive.yaml: -------------------------------------------------------------------------------- 1 | name: "Pod overrides can use $patch: replace to overwrite fields rather than merging" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | spec: 8 | securityContext: 9 | runAsUser: 1001 10 | $patch: replace 11 | components: 12 | - name: test-component 13 | container: 14 | image: test-image 15 | 16 | podTemplateSpec: 17 | metadata: 18 | labels: 19 | controller.devfile.io/devworkspace-id: test-id 20 | spec: 21 | containers: 22 | - name: test-component 23 | image: test-image 24 | securityContext: 25 | fsGroup: 2000 26 | runAsGroup: 3000 27 | runAsUser: 1000 28 | 29 | 30 | output: 31 | podTemplateSpec: 32 | metadata: 33 | labels: 34 | controller.devfile.io/devworkspace-id: test-id 35 | spec: 36 | containers: 37 | - name: test-component 38 | image: test-image 39 | securityContext: 40 | runAsUser: 1001 41 | -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/workspace-component-attribute.yaml: -------------------------------------------------------------------------------- 1 | name: "Workspace defines pod overrides in component attribute" 2 | 3 | input: 4 | workspace: 5 | components: 6 | - name: test-component 7 | attributes: 8 | pod-overrides: 9 | metadata: 10 | labels: 11 | test-label: test-value 12 | container: 13 | image: test-image 14 | 15 | podTemplateSpec: 16 | metadata: 17 | labels: 18 | controller.devfile.io/devworkspace-id: test-id 19 | spec: 20 | containers: 21 | - name: test-component 22 | image: test-image 23 | 24 | output: 25 | podTemplateSpec: 26 | metadata: 27 | labels: 28 | controller.devfile.io/devworkspace-id: test-id 29 | test-label: test-value 30 | spec: 31 | containers: 32 | - name: test-component 33 | image: test-image -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/workspace-defines-attribute-in-non-container-component.yaml: -------------------------------------------------------------------------------- 1 | name: "Workspace defines attribute in non-container components" 2 | 3 | input: 4 | workspace: 5 | components: 6 | - name: test-component 7 | container: 8 | image: test-image 9 | - name: test-volume 10 | attributes: 11 | pod-overrides: 12 | metadata: 13 | labels: 14 | test-label: test-value 15 | volume: {} 16 | 17 | podTemplateSpec: 18 | metadata: 19 | labels: 20 | controller.devfile.io/devworkspace-id: test-id 21 | spec: 22 | containers: 23 | - name: test-component 24 | image: test-image 25 | 26 | output: 27 | podTemplateSpec: 28 | metadata: 29 | labels: 30 | controller.devfile.io/devworkspace-id: test-id 31 | test-label: test-value 32 | spec: 33 | containers: 34 | - name: test-component 35 | image: test-image -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/workspace-global-attribute-as-json.yaml: -------------------------------------------------------------------------------- 1 | name: "Workspace defines pod overrides in global attribute specified as json" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: {"metadata": {"labels": {"test-label": "test-value"}}} 7 | components: 8 | - name: test-component 9 | container: 10 | image: test-image 11 | 12 | podTemplateSpec: 13 | metadata: 14 | labels: 15 | controller.devfile.io/devworkspace-id: test-id 16 | spec: 17 | containers: 18 | - name: test-component 19 | image: test-image 20 | 21 | output: 22 | podTemplateSpec: 23 | metadata: 24 | labels: 25 | controller.devfile.io/devworkspace-id: test-id 26 | test-label: test-value 27 | spec: 28 | containers: 29 | - name: test-component 30 | image: test-image -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/workspace-global-attribute.yaml: -------------------------------------------------------------------------------- 1 | name: "Workspace defines pod overrides in global attribute" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | metadata: 8 | labels: 9 | test-label: test-value 10 | components: 11 | - name: test-component 12 | container: 13 | image: test-image 14 | 15 | podTemplateSpec: 16 | metadata: 17 | labels: 18 | controller.devfile.io/devworkspace-id: test-id 19 | spec: 20 | containers: 21 | - name: test-component 22 | image: test-image 23 | 24 | output: 25 | podTemplateSpec: 26 | metadata: 27 | labels: 28 | controller.devfile.io/devworkspace-id: test-id 29 | test-label: test-value 30 | spec: 31 | containers: 32 | - name: test-component 33 | image: test-image -------------------------------------------------------------------------------- /pkg/library/overrides/testdata/pod-overrides/workspace-multiple-attributes.yaml: -------------------------------------------------------------------------------- 1 | name: "Workspace global attributes overrides component attributes" 2 | 3 | input: 4 | workspace: 5 | attributes: 6 | pod-overrides: 7 | metadata: 8 | labels: 9 | test-label: global-label 10 | components: 11 | - name: test-component 12 | attributes: 13 | pod-overrides: 14 | metadata: 15 | labels: 16 | test-label: component-label 17 | container: 18 | image: test-image 19 | 20 | podTemplateSpec: 21 | metadata: 22 | labels: 23 | controller.devfile.io/devworkspace-id: test-id 24 | spec: 25 | containers: 26 | - name: test-component 27 | image: test-image 28 | 29 | output: 30 | podTemplateSpec: 31 | metadata: 32 | labels: 33 | controller.devfile.io/devworkspace-id: test-id 34 | test-label: global-label 35 | spec: 36 | containers: 37 | - name: test-component 38 | image: test-image -------------------------------------------------------------------------------- /pkg/provision/automount/testdata/errorBadAccessMode.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when access mode is invalid" 2 | 3 | input: 4 | configmaps: 5 | - 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: test-file-configmap 10 | labels: 11 | controller.devfile.io/mount-to-devworkspace: "true" 12 | controller.devfile.io/watch-configmap: 'true' 13 | annotations: 14 | controller.devfile.io/mount-as: file 15 | controller.devfile.io/mount-path: /tmp/configmap/file 16 | controller.devfile.io/mount-access-mode: "777" # Not parsed as octal 17 | data: 18 | configmap-key: "hello" 19 | 20 | output: 21 | errRegexp: "invalid access mode annotation: value '777' parsed to 1411 \\(octal\\)" 22 | -------------------------------------------------------------------------------- /pkg/provision/storage/asyncstorage/errors.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package asyncstorage 17 | 18 | import "errors" 19 | 20 | var NotReadyError = errors.New("async storage component is not ready") 21 | -------------------------------------------------------------------------------- /pkg/provision/storage/asyncstorage/volume.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package asyncstorage 17 | 18 | import ( 19 | corev1 "k8s.io/api/core/v1" 20 | "k8s.io/utils/pointer" 21 | ) 22 | 23 | func GetVolumeFromSecret(secret *corev1.Secret) *corev1.Volume { 24 | return &corev1.Volume{ 25 | Name: asyncSecretVolumeName, 26 | VolumeSource: corev1.VolumeSource{ 27 | Secret: &corev1.SecretVolumeSource{ 28 | SecretName: secret.Name, 29 | DefaultMode: pointer.Int32(0640), 30 | }, 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/can-make-projects-ephemeral.yaml: -------------------------------------------------------------------------------- 1 | name: "Can make projects volume ephemeral" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | mountSources: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | 24 | output: 25 | podAdditions: 26 | containers: 27 | - name: testing-container-1 28 | image: testing-image 29 | volumeMounts: 30 | - name: projects 31 | mountPath: "/projects-mountpath" 32 | 33 | volumes: 34 | - name: projects 35 | emptyDir: {} 36 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/can-set-ephemeral-volume-size.yaml: -------------------------------------------------------------------------------- 1 | name: "Can make projects volume ephemeral" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | mountSources: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | size: 512Mi 24 | 25 | output: 26 | podAdditions: 27 | containers: 28 | - name: testing-container-1 29 | image: testing-image 30 | volumeMounts: 31 | - name: projects 32 | mountPath: "/projects-mountpath" 33 | 34 | volumes: 35 | - name: projects 36 | emptyDir: 37 | sizeLimit: 512Mi 38 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/does-nothing-for-no-storage-needed.yaml: -------------------------------------------------------------------------------- 1 | name: "Does not modify PodAdditions when storage is not required" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image-1 9 | imagePullPolicy: Always 10 | workspace: 11 | components: 12 | - name: testing-container-1 13 | container: 14 | image: testing-image-1 15 | mountSources: false 16 | 17 | output: 18 | podAdditions: 19 | containers: 20 | - name: testing-container-1 21 | image: testing-image-1 22 | imagePullPolicy: Always 23 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/error-duplicate-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when workspace defines duplicate volumes" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | - name: "my-defined-volume" 13 | mountPath: "/test-1" 14 | 15 | workspace: 16 | components: 17 | - name: testing-container-1 18 | container: 19 | image: testing-image-1 20 | sourceMapping: "/plugins-mountpath" 21 | - name: my-defined-volume 22 | volume: {} 23 | - name: my-defined-volume 24 | volume: {} 25 | 26 | output: 27 | errRegexp: "volume component 'my-defined-volume' is defined multiple times" 28 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/error-undefined-volume-init-container.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error on undefined volume in init container" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | initContainers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | - name: "my-defined-volume" 13 | mountPath: "/test-1" 14 | 15 | workspace: 16 | components: 17 | - name: testing-container-1 18 | container: 19 | image: testing-image-1 20 | sourceMapping: "/plugins-mountpath" 21 | 22 | 23 | output: 24 | errRegexp: "container 'testing-container-1' references undefined volume 'my-defined-volume'" 25 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/error-undefined-volume.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error on undefined volume in container" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | - name: "my-defined-volume" 13 | mountPath: "/test-1" 14 | 15 | workspace: 16 | components: 17 | - name: testing-container-1 18 | container: 19 | image: testing-image-1 20 | sourceMapping: "/plugins-mountpath" 21 | 22 | 23 | output: 24 | errRegexp: "container 'testing-container-1' references undefined volume 'my-defined-volume'" 25 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/error-unparseable-ephemeral-size.yaml: -------------------------------------------------------------------------------- 1 | name: "Can make projects volume ephemeral" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | mountSources: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | size: 512XX 24 | 25 | output: 26 | errRegexp: "failed to parse size for Volume projects.*" 27 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/handles-ephemeral-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: "Handles ephemeral volumes" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: testvol 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | mountSources: false 19 | 20 | - name: testvol 21 | volume: 22 | ephemeral: true 23 | 24 | output: 25 | podAdditions: 26 | containers: 27 | - name: testing-container-1 28 | image: testing-image 29 | volumeMounts: 30 | - name: testvol 31 | mountPath: "/projects-mountpath" 32 | 33 | volumes: 34 | - name: testvol 35 | emptyDir: {} 36 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/per-user-alias.yaml: -------------------------------------------------------------------------------- 1 | name: "per-user storage class can be used as an alias for common storage class" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | workspace: 6 | attributes: 7 | controller.devfile.io/storage-type: per-user 8 | 9 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/common-storage/projects-volume-overriding.yaml: -------------------------------------------------------------------------------- 1 | name: "User can specify a projects volume manually" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | - name: projects 20 | volume: {} 21 | 22 | output: 23 | podAdditions: 24 | containers: 25 | - name: testing-container-1 26 | image: testing-image 27 | volumeMounts: 28 | - name: claim-devworkspace 29 | subPath: "test-workspaceid/projects" 30 | mountPath: "/projects-mountpath" 31 | 32 | volumes: 33 | - name: claim-devworkspace 34 | persistentVolumeClaim: 35 | claimName: claim-devworkspace 36 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/can-make-projects-ephemeral.yaml: -------------------------------------------------------------------------------- 1 | name: "Can make projects volume ephemeral" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | mountSources: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | 24 | output: 25 | podAdditions: 26 | containers: 27 | - name: testing-container-1 28 | image: testing-image 29 | volumeMounts: 30 | - name: projects 31 | mountPath: "/projects-mountpath" 32 | 33 | volumes: 34 | - name: projects 35 | emptyDir: {} 36 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/can-set-ephemeral-volume-size.yaml: -------------------------------------------------------------------------------- 1 | name: "Can set ephemeral volume size" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | mountSources: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | size: 512Mi 24 | 25 | output: 26 | podAdditions: 27 | containers: 28 | - name: testing-container-1 29 | image: testing-image 30 | volumeMounts: 31 | - name: projects 32 | mountPath: "/projects-mountpath" 33 | 34 | volumes: 35 | - name: projects 36 | emptyDir: 37 | sizeLimit: 512Mi 38 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/does-nothing-for-no-storage-needed.yaml: -------------------------------------------------------------------------------- 1 | name: "Does not modify PodAdditions when storage is not required" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image-1 9 | imagePullPolicy: Always 10 | workspace: 11 | components: 12 | - name: testing-container-1 13 | container: 14 | image: testing-image-1 15 | mountSources: false 16 | 17 | output: 18 | podAdditions: 19 | containers: 20 | - name: testing-container-1 21 | image: testing-image-1 22 | imagePullPolicy: Always 23 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/error-duplicate-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error when workspace defines duplicate volumes" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | - name: "my-defined-volume" 13 | mountPath: "/test-1" 14 | 15 | workspace: 16 | components: 17 | - name: testing-container-1 18 | container: 19 | image: testing-image-1 20 | sourceMapping: "/plugins-mountpath" 21 | - name: my-defined-volume 22 | volume: {} 23 | - name: my-defined-volume 24 | volume: {} 25 | 26 | output: 27 | errRegexp: "volume component 'my-defined-volume' is defined multiple times" 28 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/error-undefined-volume-init-container.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error on undefined volume in init container" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | initContainers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | - name: "my-defined-volume" 13 | mountPath: "/test-1" 14 | 15 | workspace: 16 | components: 17 | - name: testing-container-1 18 | container: 19 | image: testing-image-1 20 | sourceMapping: "/plugins-mountpath" 21 | 22 | 23 | output: 24 | errRegexp: "container 'testing-container-1' references undefined volume 'my-defined-volume'" 25 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/error-undefined-volume.yaml: -------------------------------------------------------------------------------- 1 | name: "Returns error on undefined volume in container" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | - name: "my-defined-volume" 13 | mountPath: "/test-1" 14 | 15 | workspace: 16 | components: 17 | - name: testing-container-1 18 | container: 19 | image: testing-image-1 20 | sourceMapping: "/plugins-mountpath" 21 | 22 | 23 | output: 24 | errRegexp: "container 'testing-container-1' references undefined volume 'my-defined-volume'" 25 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/error-unparseable-ephemeral-size.yaml: -------------------------------------------------------------------------------- 1 | name: "Can make projects volume ephemeral" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | mountSources: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | size: 512XX 24 | 25 | output: 26 | errRegexp: "failed to parse size for Volume projects.*" 27 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/handles-ephemeral-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: "Handles ephemeral volumes" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: testvol 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | mountSources: false 19 | 20 | - name: testvol 21 | volume: 22 | ephemeral: true 23 | 24 | output: 25 | podAdditions: 26 | containers: 27 | - name: testing-container-1 28 | image: testing-image 29 | volumeMounts: 30 | - name: testvol 31 | mountPath: "/projects-mountpath" 32 | 33 | volumes: 34 | - name: testvol 35 | emptyDir: {} 36 | -------------------------------------------------------------------------------- /pkg/provision/storage/testdata/perWorkspace-storage/projects-volume-overriding.yaml: -------------------------------------------------------------------------------- 1 | name: "User can specify a projects volume manually" 2 | 3 | input: 4 | devworkspaceId: "test-workspaceid" 5 | podAdditions: 6 | containers: 7 | - name: testing-container-1 8 | image: testing-image 9 | volumeMounts: 10 | - name: "projects" 11 | mountPath: "/projects-mountpath" 12 | 13 | workspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image-1 18 | sourceMapping: "/plugins-mountpath" 19 | - name: projects 20 | volume: {} 21 | 22 | output: 23 | podAdditions: 24 | containers: 25 | - name: testing-container-1 26 | image: testing-image 27 | volumeMounts: 28 | - name: storage-test-workspaceid 29 | subPath: "projects" 30 | mountPath: "/projects-mountpath" 31 | 32 | volumes: 33 | - name: storage-test-workspaceid 34 | persistentVolumeClaim: 35 | claimName: storage-test-workspaceid 36 | -------------------------------------------------------------------------------- /pkg/provision/workspace/ssh-askpass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PASSPHRASE_FILE_PATH="/etc/ssh/passphrase" 3 | if [ ! -f $PASSPHRASE_FILE_PATH ]; then 4 | echo "Error: passphrase file is missing in the '/etc/ssh/' directory" 1>&2 5 | exit 1 6 | fi 7 | cat $PASSPHRASE_FILE_PATH 8 | -------------------------------------------------------------------------------- /pkg/webhook/service/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package service 17 | 18 | import logf "sigs.k8s.io/controller-runtime/pkg/log" 19 | 20 | var log = logf.Log.WithName("service") 21 | -------------------------------------------------------------------------------- /samples/code-latest.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: code-latest 5 | spec: 6 | started: true 7 | template: 8 | projects: 9 | - name: web-nodejs-sample 10 | git: 11 | remotes: 12 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 13 | components: 14 | - name: dev 15 | container: 16 | image: quay.io/devfile/universal-developer-image:latest 17 | memoryLimit: 512Mi 18 | memoryRequest: 256Mi 19 | cpuRequest: 1000m 20 | commands: 21 | - id: say-hello 22 | exec: 23 | component: dev 24 | commandLine: echo "Hello from $(pwd)" 25 | workingDir: ${PROJECT_SOURCE}/app 26 | contributions: 27 | - name: che-code 28 | uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-code/latest/devfile.yaml 29 | components: 30 | - name: che-code-runtime-description 31 | container: 32 | env: 33 | - name: CODE_HOST 34 | value: 0.0.0.0 35 | -------------------------------------------------------------------------------- /samples/container-overrides.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: web-terminal-container-overrides 5 | spec: 6 | started: true 7 | template: 8 | attributes: 9 | controller.devfile.io/storage-type: ephemeral 10 | projects: 11 | - name: web-nodejs-sample 12 | git: 13 | remotes: 14 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 15 | components: 16 | - name: web-terminal 17 | attributes: 18 | container-overrides: {"resources":{"limits":{"nvidia.com/gpu":"1"}}} 19 | container: 20 | image: quay.io/wto/web-terminal-tooling:next 21 | args: 22 | - tail 23 | - '-f' 24 | - /dev/null 25 | cpuLimit: 400m 26 | cpuRequest: 100m 27 | memoryLimit: 256Mi 28 | memoryRequest: 128Mi 29 | -------------------------------------------------------------------------------- /samples/empty.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: empty-devworkspace 5 | spec: 6 | started: true 7 | template: {} 8 | -------------------------------------------------------------------------------- /samples/ephemeral-storage.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: code-latest-ephemeral 5 | spec: 6 | started: true 7 | template: 8 | attributes: 9 | controller.devfile.io/storage-type: ephemeral 10 | projects: 11 | - name: web-nodejs-sample 12 | git: 13 | remotes: 14 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 15 | commands: 16 | - id: say-hello 17 | exec: 18 | component: che-code-runtime-description 19 | commandLine: echo "Hello from $(pwd)" 20 | workingDir: ${PROJECT_SOURCE}/app 21 | contributions: 22 | - name: che-code 23 | uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-code/latest/devfile.yaml 24 | components: 25 | - name: che-code-runtime-description 26 | container: 27 | env: 28 | - name: CODE_HOST 29 | value: 0.0.0.0 30 | -------------------------------------------------------------------------------- /samples/git-clone-sample.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: git-clone-sample-devworkspace 5 | spec: 6 | started: true 7 | template: 8 | projects: 9 | - name: web-nodejs-sample 10 | git: 11 | remotes: 12 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 13 | - name: devworkspace-operator 14 | git: 15 | checkoutFrom: 16 | remote: origin 17 | revision: 0.21.x 18 | remotes: 19 | origin: "https://github.com/devfile/devworkspace-operator.git" 20 | amisevsk: "https://github.com/amisevsk/devworkspace-operator.git" 21 | commands: 22 | - id: say-hello 23 | exec: 24 | component: che-code-runtime-description 25 | commandLine: echo "Hello from $(pwd)" 26 | workingDir: ${PROJECT_SOURCE}/app 27 | contributions: 28 | - name: che-code 29 | uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-code/latest/devfile.yaml 30 | components: 31 | - name: che-code-runtime-description 32 | container: 33 | env: 34 | - name: CODE_HOST 35 | value: 0.0.0.0 36 | -------------------------------------------------------------------------------- /samples/idea-latest.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: idea-latest 5 | spec: 6 | started: true 7 | template: 8 | projects: 9 | - name: python-hello-world 10 | git: 11 | remotes: 12 | origin: "https://github.com/che-samples/python-hello-world.git" 13 | components: 14 | - name: dev 15 | container: 16 | image: quay.io/devfile/universal-developer-image:latest 17 | memoryLimit: 512Mi 18 | memoryRequest: 256Mi 19 | cpuRequest: 1000m 20 | contributions: 21 | - name: che-idea 22 | uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-idea/latest/devfile.yaml 23 | -------------------------------------------------------------------------------- /samples/per-workspace-storage.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: code-latest-per-workspace 5 | spec: 6 | started: true 7 | template: 8 | attributes: 9 | controller.devfile.io/storage-type: per-workspace 10 | projects: 11 | - name: web-nodejs-sample 12 | git: 13 | remotes: 14 | origin: "https://github.com/che-samples/web-nodejs-sample.git" 15 | commands: 16 | - id: say-hello 17 | exec: 18 | component: che-code-runtime-description 19 | commandLine: echo "Hello from $(pwd)" 20 | workingDir: ${PROJECT_SOURCE}/app 21 | contributions: 22 | - name: che-code 23 | uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-code/latest/devfile.yaml 24 | components: 25 | - name: che-code-runtime-description 26 | container: 27 | env: 28 | - name: CODE_HOST 29 | value: 0.0.0.0 30 | -------------------------------------------------------------------------------- /samples/plain.yaml: -------------------------------------------------------------------------------- 1 | kind: DevWorkspace 2 | apiVersion: workspace.devfile.io/v1alpha2 3 | metadata: 4 | name: plain-devworkspace 5 | spec: 6 | started: true 7 | routingClass: 'basic' 8 | template: 9 | components: 10 | - name: web-terminal 11 | container: 12 | image: quay.io/wto/web-terminal-tooling:next 13 | memoryRequest: 256Mi 14 | memoryLimit: 512Mi 15 | mountSources: true 16 | command: 17 | - "tail" 18 | - "-f" 19 | - "/dev/null" 20 | -------------------------------------------------------------------------------- /samples/plugins/web-terminal-exec.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: workspace.devfile.io/v1alpha2 2 | kind: DevWorkspaceTemplate 3 | metadata: 4 | name: web-terminal-exec 5 | labels: 6 | console.openshift.io/terminal: 'true' 7 | annotations: 8 | controller.devfile.io/allow-import-from: '*' 9 | spec: 10 | components: 11 | - name: web-terminal-exec 12 | container: 13 | image: quay.io/wto/web-terminal-exec:next 14 | command: 15 | - /go/bin/che-machine-exec 16 | - '--authenticated-user-id' 17 | - $(DEVWORKSPACE_CREATOR) 18 | - '--idle-timeout' 19 | - $(WEB_TERMINAL_IDLE_TIMEOUT) 20 | - '--pod-selector' 21 | - controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID) 22 | - '--use-tls' 23 | - '--use-bearer-token' 24 | env: 25 | - name: WEB_TERMINAL_IDLE_TIMEOUT 26 | value: 15m 27 | sourceMapping: /projects 28 | mountSources: false 29 | cpuRequest: 100m 30 | memoryRequest: 128Mi 31 | cpuLimit: 400m 32 | memoryLimit: 128Mi 33 | endpoints: 34 | - attributes: 35 | type: main 36 | exposure: public 37 | name: exec 38 | protocol: http 39 | targetPort: 4444 40 | -------------------------------------------------------------------------------- /samples/plugins/web-terminal-tooling.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: workspace.devfile.io/v1alpha2 2 | kind: DevWorkspaceTemplate 3 | metadata: 4 | name: web-terminal-tooling 5 | labels: 6 | console.openshift.io/terminal: 'true' 7 | annotations: 8 | controller.devfile.io/allow-import-from: '*' 9 | spec: 10 | components: 11 | - name: web-terminal-tooling 12 | container: 13 | image: quay.io/wto/web-terminal-tooling:next 14 | args: 15 | - tail 16 | - '-f' 17 | - /dev/null 18 | cpuLimit: 400m 19 | cpuRequest: 100m 20 | memoryLimit: 256Mi 21 | memoryRequest: 128Mi 22 | mountSources: false 23 | sourceMapping: /projects 24 | -------------------------------------------------------------------------------- /samples/web-terminal.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: workspace.devfile.io/v1alpha2 2 | kind: DevWorkspace 3 | metadata: 4 | name: web-terminal 5 | labels: 6 | console.openshift.io/terminal: 'true' 7 | annotations: 8 | controller.devfile.io/restricted-access: 'true' 9 | spec: 10 | routingClass: web-terminal 11 | started: true 12 | template: 13 | components: 14 | - name: web-terminal-tooling 15 | plugin: 16 | kubernetes: 17 | name: web-terminal-tooling 18 | - name: web-terminal-exec 19 | plugin: 20 | kubernetes: 21 | name: web-terminal-exec 22 | -------------------------------------------------------------------------------- /test/e2e/pkg/config/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package config 17 | 18 | import "github.com/devfile/devworkspace-operator/test/e2e/pkg/client" 19 | 20 | var OperatorNamespace string 21 | var DevWorkspaceNamespace string 22 | 23 | var DevK8sClient, AdminK8sClient *client.K8sClient 24 | -------------------------------------------------------------------------------- /test/e2e/pkg/metadata/metadata.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package metadata 17 | 18 | type OperatorNamespace struct { 19 | Name string 20 | } 21 | 22 | var Namespace = OperatorNamespace{} 23 | -------------------------------------------------------------------------------- /test/resources/restricted-access-devworkspace.yaml: -------------------------------------------------------------------------------- 1 | # This file demostrates basic support for restricting access to DevWorkspace containers. 2 | # It is mainly used in e2e tests. 3 | kind: DevWorkspace 4 | apiVersion: workspace.devfile.io/v1alpha2 5 | metadata: 6 | name: restricted-access 7 | annotations: 8 | controller.devfile.io/restricted-access: "true" 9 | labels: 10 | # it's a label OpenShift console uses a flag to mark terminal's workspaces 11 | console.openshift.io/terminal: "true" 12 | spec: 13 | started: true 14 | routingClass: 'web-terminal' 15 | template: 16 | components: 17 | - name: restricted-access-container 18 | container: 19 | image: quay.io/wto/web-terminal-tooling:latest 20 | mountSources: false 21 | args: ["tail", "-f", "/dev/null"] 22 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package version 17 | 18 | var ( 19 | // Version is the operator version 20 | Version = "v0.35.0-dev" 21 | // Commit is the commit hash corresponding to the code that was built. Can be suffixed with `-dirty` 22 | Commit string = "unknown" 23 | // BuildTime is the time of build of the binary 24 | BuildTime string = "unknown" 25 | ) 26 | -------------------------------------------------------------------------------- /webhook/workspace/handler/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package handler 17 | 18 | import logf "sigs.k8s.io/controller-runtime/pkg/log" 19 | 20 | var log = logf.Log.WithName("webhook.workspace.handler") 21 | -------------------------------------------------------------------------------- /webhook/workspace/handler/testdata/warning/add-unsupported-features-when-none-present.yaml: -------------------------------------------------------------------------------- 1 | name: "Add unsupported devfile features when initial workspace did not contain any unsupported features" 2 | 3 | input: 4 | oldWorkspace: 5 | components: 6 | - name: testing-container-1 7 | container: 8 | image: testing-image 9 | - name: projects 10 | volume: 11 | ephemeral: true 12 | 13 | newWorkspace: 14 | components: 15 | - name: testing-container-1 16 | container: 17 | image: testing-image 18 | dedicatedPod: true 19 | - name: projects 20 | volume: 21 | ephemeral: true 22 | size: "10Gi" 23 | 24 | output: 25 | expectedWarning: "Unsupported Devfile features are present in this workspace. The following features will have no effect: components[].container.dedicatedPod, used by components: testing-container-1" 26 | newWarningsPresent: true 27 | -------------------------------------------------------------------------------- /webhook/workspace/handler/testdata/warning/add-unsupported-features-when-some-present.yaml: -------------------------------------------------------------------------------- 1 | name: "Add unsupported devfile features to workspace that already contains unsupported features" 2 | 3 | input: 4 | oldWorkspace: 5 | components: 6 | - name: testing-container-1 7 | container: 8 | image: testing-image 9 | dedicatedPod: true 10 | - name: projects 11 | volume: 12 | ephemeral: true 13 | 14 | newWorkspace: 15 | components: 16 | - name: testing-container-1 17 | container: 18 | image: testing-image 19 | dedicatedPod: true 20 | - name: projects 21 | volume: 22 | ephemeral: true 23 | size: "10Gi" 24 | - name: image-component 25 | image: 26 | imageName: python-image:latest 27 | autoBuild: true 28 | dockerfile: 29 | uri: docker/Dockerfile 30 | args: 31 | - 'MY_ENV=/home/path' 32 | buildContext: . 33 | rootRequired: false 34 | 35 | output: 36 | expectedWarning: "Unsupported Devfile features are present in this workspace. The following features will have no effect: components[].image, used by components: image-component" 37 | newWarningsPresent: true 38 | -------------------------------------------------------------------------------- /webhook/workspace/handler/testdata/warning/remove-all-unsupported-features.yaml: -------------------------------------------------------------------------------- 1 | name: "Remove all unsupported devfile features from workspace" 2 | 3 | input: 4 | oldWorkspace: 5 | components: 6 | - name: testing-container-1 7 | container: 8 | image: testing-image 9 | dedicatedPod: true 10 | endpoints: 11 | - name: web 12 | targetPort: 8080 13 | exposure: public 14 | annotation: 15 | key: value 16 | - name: projects 17 | volume: 18 | ephemeral: true 19 | 20 | newWorkspace: 21 | components: 22 | - name: testing-container-1 23 | container: 24 | image: testing-image 25 | - name: projects 26 | volume: 27 | ephemeral: true 28 | 29 | output: 30 | newWarningsPresent: false 31 | -------------------------------------------------------------------------------- /webhook/workspace/handler/testdata/warning/remove-single-unsupported-feature.yaml: -------------------------------------------------------------------------------- 1 | name: "Remove single unsupported devfile features from workspace" 2 | 3 | input: 4 | oldWorkspace: 5 | components: 6 | - name: testing-container-1 7 | container: 8 | image: testing-image 9 | dedicatedPod: true 10 | endpoints: 11 | - name: web 12 | targetPort: 8080 13 | exposure: public 14 | annotation: 15 | key: value 16 | - name: projects 17 | volume: 18 | ephemeral: true 19 | 20 | newWorkspace: 21 | components: 22 | - name: testing-container-1 23 | container: 24 | image: testing-image 25 | dedicatedPod: true 26 | - name: projects 27 | volume: 28 | ephemeral: true 29 | 30 | output: 31 | newWarningsPresent: false 32 | -------------------------------------------------------------------------------- /webhook/workspace/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2025 Red Hat, Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | package workspace 17 | 18 | import logf "sigs.k8s.io/controller-runtime/pkg/log" 19 | 20 | var log = logf.Log.WithName("webhook.devworkspace") 21 | --------------------------------------------------------------------------------