Skip to content

feat: Manage OpenVSX as Che operand#2130

Open
svor wants to merge 49 commits into
mainfrom
sv-openvsx
Open

feat: Manage OpenVSX as Che operand#2130
svor wants to merge 49 commits into
mainfrom
sv-openvsx

Conversation

@svor

@svor svor commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

  • Deploy and manage a dedicated OpenVSX server and PostgreSQL database as Che operands,
    enabled via spec.components.openVSX.enable
  • Expose OpenVSX via a dedicated Ingress (not the Che gateway), supporting both OpenShift
    and Kubernetes
  • Auto-seed the database with a privileged user and admin PAT via a one-shot Job
  • Support publishing extensions from a user-editable openvsx-extensions ConfigMap
  • Persist extension storage on a PVC (default 3Gi, configurable via
    spec.components.openVSX.server.claimSize)
  • Make openvsx-server-config ConfigMap user-editable with automatic pod rollout on
    changes

New CRD fields

  • spec.components.openVSX.enable — enables the OpenVSX operand (default is false)
  • spec.components.openVSX.server.claimSize — PVC size for extension storage (default:
    3Gi)
  • spec.components.openVSX.server.deployment — deployment overrides
  • spec.components.openVSX.database.claimSize — PVC size for PostgreSQL data (default:
    1Gi)
  • spec.components.openVSX.database.deployment — deployment overrides
  • status.openVSXURL — URL of the managed OpenVSX instance

Resources created when enabled

  • openvsx-database: Deployment, Service, PVC, credentials Secret
  • openvsx-server: Deployment, Service, PVC, ConfigMap (application config), ConfigMap
    (extensions list)
  • openvsx-user-setup: one-shot Job to seed DB users and PATs
  • openvsx-extension-publish: Job to publish extensions from the extensions ConfigMap
  • openvsx: Ingress with platform-appropriate TLS and annotations

All resources are cleaned up when openVSX.enable is set to false.

Screenshot/screencast of this PR

Screenshot 2026-06-05 at 14 33 26 Screenshot 2026-06-05 at 14 33 51

What issues does this PR fix or reference?

https://issues.redhat.com/browse/CRW-10323

How to test this PR?

  1. Deploy the operator:

OpenShift

./build/scripts/olm/test-catalog-from-sources.sh

or

build/scripts/docker-run.sh /bin/bash -c "
  oc login \
    --token=<...> \
    --server=<...> \
    --insecure-skip-tls-verify=true && \
  build/scripts/olm/test-catalog-from-sources.sh
"

on Minikube

./build/scripts/minikube-tests/test-operator-from-sources.sh

Common Test Scenarios

  • Enable OpenVSX operand and verify all resources are created
  • Publish extensions via openvsx-extensions ConfigMap
Screenshot 2026-06-08 at 15 13 33 Screenshot 2026-06-08 at 15 13 42
  • Verify extensions survive pod restart (PVC persistence)
  • Update openvsx-server-config ConfigMap and verify pod rollout
Screenshot 2026-06-08 at 15 29 40 Screenshot 2026-06-08 at 15 31 05
  • Resize PVC via claimSize and verify expansion
  • Disable OpenVSX operand and verify all resources are deleted
  • Verify Ingress works on both OpenShift and Kubernetes

PR Checklist

As the author of this Pull Request I made sure that:

Reviewers

Reviewers, please comment how you tested the PR when approving it.

svor and others added 30 commits June 5, 2026 11:50
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
…bled to enable

Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
The OpenVSX WebUI cannot work behind the Che gateway path prefix
(/openvsx) because its JS derives API URLs from location.host and
makes calls to root paths (/api, /user, /login, etc.), and static
assets use absolute root paths (/bundle-xxx.js).

A dedicated Kubernetes Ingress with its own hostname provides
multi-path routing: server API paths go to openvsx-server:8080,
everything else goes to openvsx-webui:3000. This works on both
Kubernetes (nginx ingress) and OpenShift 4.x (HAProxy ingress
controller handles Ingress resources natively), matching the
upstream eclipse-openvsx deployment pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
On OpenShift the ingress controller is HAProxy with class
openshift-default, not nginx. Omitting ingressClassName lets
the platform use its default class.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
On OpenShift, the ingress-to-route controller rejects Ingress resources
with an empty TLS secretName ("Invalid or missing TLS secret"). Instead,
omit the TLS section entirely and let the router use its default wildcard
certificate. Add route.openshift.io/termination=edge annotation so the
generated Route uses TLS edge termination.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
The openvsx-server image now includes the WebUI (static assets copied
into BOOT-INF/classes/static/). This eliminates the need for a separate
webui component and the custom multi-path Ingress.

- Remove webui reconciler, constants, defaults, and API type
- Remove dedicated Ingress expose reconciler
- Expose OpenVSX through Che Traefik gateway (same pattern as Dashboard
  and PluginRegistry) under /openvsx path prefix
- Set server.servlet.context-path=/openvsx so Spring Boot serves
  everything under /openvsx natively (no gateway strip-prefix needed)
- Rename RELATED_IMAGE_openvsx_server to RELATED_IMAGE_openvsx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
Add /openvsx to oauth-proxy skip_auth_regex so OpenVSX is accessible
without authentication, same pattern as plugin-registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a PVC is already bound, Kubernetes populates volumeName and
storageClassName which are immutable. The sync was trying to update
these fields, causing reconciliation to fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert deepcopy, CRD, CSV, and combined YAML files to their
state on main. These will be regenerated properly later.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
Status.Phase is set to "Pending" by Kubernetes immediately after PVC
creation, causing a diff against the blueprint which has empty status.
This triggered an update that failed on immutable spec fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without explicit RestartPolicy, the blueprint has empty string while
Kubernetes sets it to "Always", causing a diff on every reconciliation
and blocking subsequent reconcilers (including gateway) from running.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
The OpenVSX webui (Vite base='/') uses absolute root paths for static
assets (/bundle-*.js), which is incompatible with the Che gateway's
path-prefix routing. Switch to a dedicated Ingress with its own hostname
(openvsx-<ns>.<domain>), matching the upstream deployment pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
Add a Kubernetes Job that runs after the OpenVSX server deployment is
ready, inserting a privileged user and personal access token into the
database for the ovsx CLI to publish extensions. The Job uses idempotent
SQL (ON CONFLICT DO NOTHING) and reads credentials from the existing
openvsx-postgres-credentials secret, which now includes userName and
userPAT fields with defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The user setup Job was failing because the readiness probe passes
before Flyway finishes creating tables. Add a wait-for-schema init
container that polls until the user_data table exists, ensuring the
main container's INSERT statements succeed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add openvsx-admin user (role: admin) with PAT to the database seeding
Job alongside the existing publisher user. Propagate OPENVSX_USER_PAT,
OPENVSX_ADMIN_PAT, and OPENVSX_URL env vars into the server deployment
so they are available inside the pod.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mount the ca-certs-merged ConfigMap into the server pod and set
NODE_EXTRA_CA_CERTS so the ovsx CLI trusts self-signed certificates.
Rename OPENVSX_URL to OVSX_REGISTRY_URL to match the env var name
the ovsx CLI expects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce a user-editable ConfigMap (openvsx-extensions) that holds a list
of VSIX URLs and a Job that downloads and publishes them to the OpenVSX
registry. The ConfigMap is created with empty defaults only if absent,
preserving user modifications. A content hash env var triggers Job
recreation when extensions change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TTL controller was deleting completed Jobs before the next reconcile,
causing deploy.Sync to recreate them and orphan the old pods. Without
TTL, completed Jobs persist and deploy.Sync skips them when no spec
diff is detected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch to create-only pattern so user modifications are preserved across
reconcile loops. Add CM_REVISION env var to trigger pod rollout when the
ConfigMap content changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch to create-only pattern so the Job is created on first reconcile
and skipped on subsequent ones, preventing unnecessary re-runs when
other resources like ConfigMaps are updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ephemeral /tmp/extensions with a PersistentVolumeClaim to persist
published extensions across pod restarts and avoid storage limits that
cause 406 errors with many extensions. Configurable via server.claimSize
in the CRD, defaults to 2Gi.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Valerii Svydenko <vsvydenk@redhat.com>
Increase OpenVSX server memory limit to 2Gi and request to 512Mi to
match the JVM -Xmx2048m setting, preventing OOM kills during extension
publishing. Also ignore additional immutable PVC spec fields
(VolumeAttributesClassName, DataSource, DataSourceRef) in both server
and postgres PVC diff options to prevent false diffs that cause
reconcile errors on bound PVCs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace deploy.Sync for PVC updates with manual Get/Update that only
patches resources.requests.storage. deploy.Sync sends the full PVC spec
which Kubernetes rejects because PVC spec is immutable after creation
except for resources.requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread api/v2/checluster_types.go
Comment thread api/v2/checluster_types.go Outdated
Comment thread api/v2/checluster_types.go Outdated
svor and others added 3 commits June 9, 2026 11:34
- Replace bare ClaimSize field in OpenVSXServer and OpenVSXPostgres with
  Storage *PVC to reuse existing PVC type (adds storageClass and
  storageAccessMode support)
- Add diffs.PVC comparator for PVC reconciliation via deploy.Sync
- Add missing webhook validation for Server claimSize to prevent
  runtime panics from resource.MustParse
- Change PostgreSQL deployment strategy from RollingUpdate to Recreate
  to prevent data corruption with single-replica RWO PVC

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
resource.Quantity struct equality (==) fails when values have different
internal formats (binary SI vs decimal SI), causing PVC updates to be
silently skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
deploy.Sync sends the full blueprint on update, which Kubernetes rejects
for bound PVCs because spec fields like volumeName and storageClassName
are immutable. Read the existing PVC and update only resources.requests
directly. Also only allow expansion (desiredSize > currentSize) since
most StorageClasses don't support shrinking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@eclipse-che eclipse-che deleted a comment from openshift-ci Bot Jun 9, 2026
@svor svor marked this pull request as draft June 9, 2026 16:23
svor and others added 9 commits June 11, 2026 12:54
…names

Rename the OpenVSX struct fields (Server -> OpenVSXServer, Postgres ->
OpenVSXDatabase) and the type (OpenVSXPostgres -> OpenVSXDatabase) to
use generic database terminology. Move the postgres package to database,
update constants, K8s resource names, and JDBC URL accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace deploy.DeleteNamespacedObject with the preferred
ClientWrapper.DeleteByKeyIgnoreNotFound API in OpenVSX reconcilers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace deploy.SyncSecretToCluster with direct Secret construction,
controllerutil.SetControllerReference, and ClientWrapper.Sync.
Replace deprecated k8s.io/utils/pointer with k8s.io/utils/ptr.
Remove redundant embedded field selector in test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace static userPAT and adminPAT strings with randomly generated
32-character passwords for better security.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The expose reconciler runs after the server reconciler, so
Status.OpenVSXURL is empty on the first reconcile. Defer Job
creation until the URL is available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Kubernetes Jobs are immutable after creation. Instead of relying on
deploy.Sync to update the Job (which would fail), compare the
EXTENSIONS_HASH and delete the old Job before creating a new one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@svor svor marked this pull request as ready for review June 11, 2026 16:48
Comment thread api/v2/checluster_types.go Outdated
@@ -445,6 +449,44 @@ type PluginRegistry struct {
OpenVSXURL *string `json:"openVSXURL,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still possible to configure when OpenVSX server is deployed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Update comment for OpenVSXURL, to outline that this url is not used if OpenVSX registry is deployed.
  2. Update pluginregistry.go to update status field with OpenVSXURL only if OpenVSX is not deployed
  3. Dashboard reads status.openvsxurl

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

When openVSX.enable=true, pluginRegistry.openVSXURL is ignored. The IsEmbeddedOpenVSXRegistryConfigured() method checks IsOpenVSXOperandEnabled() first and returns false immediately, bypassing the pluginRegistry.openVSXURL check entirely. The dashboard receives the managed OpenVSX URL from status.openVSXURL via an env var override.

When openVSX.enable=false, pluginRegistry.openVSXURL works as before - controls the embedded OpenVSX and plugin registry lifecycle.

Comment thread api/v2/checluster_types.go
Enable bool `json:"enable,omitempty"`
// OpenVSX server configuration.
// +optional
OpenVSXServer *OpenVSXServer `json:"server,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
OpenVSXServer *OpenVSXServer `json:"server,omitempty"`
OpenVSXServer *OpenVSXServer `json:"openVSXServer,omitempty"`

OpenVSXServer *OpenVSXServer `json:"server,omitempty"`
// Database configuration for OpenVSX.
// +optional
OpenVSXDatabase *OpenVSXDatabase `json:"database,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
OpenVSXDatabase *OpenVSXDatabase `json:"database,omitempty"`
OpenVSXDatabase *OpenVSXDatabase `json:"openVSXDatabase,omitempty"`

// +operator-sdk:csv:customresourcedefinitions:type=status,displayName="Plugin registry URL"
// +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors="urn:alm:descriptor:org.w3:link"
PluginRegistryURL string `json:"pluginRegistryURL"`
// The public URL of the managed OpenVSX registry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if pluginRegistry.openVSXURL is configured?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When openVSX.enable=true, pluginRegistry.openVSXURL is ignored. The IsEmbeddedOpenVSXRegistryConfigured() method checks IsOpenVSXOperandEnabled() first and returns false immediately, bypassing the pluginRegistry.openVSXURL check entirely. The dashboard receives the managed OpenVSX URL from status.openVSXURL via an env var override.

When openVSX.enable=false, pluginRegistry.openVSXURL works as before - controls the embedded OpenVSX and plugin registry lifecycle.

Comment thread pkg/deploy/openvsx/database/openvsx_database.go Outdated
Labels: deploy.GetLabels(defaults.GetCheFlavor()),
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should allow configure credentials by admin:

  1. Admin create a secret
  2. Set secret name in checluster CR

return &OpenVSXExposeReconciler{}
}

func (r *OpenVSXExposeReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See exposeCheEndpoint to create either ingress or route

@tolusha

tolusha commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

/che-ai-assistant ok-pr-review

Review is complete. Please check the review comments below.

@tolusha tolusha left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehensive PR Review - #2130

I've completed a comprehensive review including standard, deep, and impact analysis. Here are the key findings:

Critical Issues (Must Fix)

  1. SQL injection risk via shell variable interpolation (pkg/deploy/openvsx/server/openvsx_server.go:204)

    • User-setup Job constructs SQL via shell expansion, which becomes exploitable if admin-supplied secrets are allowed (as requested by reviewer)
    • Use parameterized queries via psql -v variables
  2. Test package imported in production code (pkg/deploy/openvsx/openvsx_expose.go:17)

    • Creates compile-time dependency on test utilities in production binaries
    • Use dependency injection instead of test.IsTestMode()
  3. CRD JSON tag naming doesn't match reviewer's explicit suggestion (api/v2/checluster_types.go:462-465)

    • Current: json:"server" and json:"database" (too generic)
    • Requested: json:"openVSXServer" and json:"openVSXDatabase"
    • Settle this before release to avoid migration later

High Priority

  1. resource.MustParse can panic the operator - Use resource.ParseQuantity in reconcile paths
  2. No logging in new reconcilers - Zero visibility into OpenVSX lifecycle operations
  3. User-setup Job stuck permanently if it fails - No retry mechanism for failed Jobs
  4. Custom ingress implementation duplicates existing infrastructure - Reviewer explicitly requested using exposeCheEndpoint
  5. No test coverage for OpenVSXExposeReconciler - Most complex reconciler has zero tests

Operational Readiness Concerns

  1. ConfigMap ResourceVersion causes unnecessary rollouts - Use content hash instead
  2. Image env vars use os.Getenv - Empty images cause cryptic failures; validate when enable: true
  3. Reconciler ordering dependency is implicit - Add documentation for the dependency chain
  4. Cleanup silently discards delete errors - At minimum, log these errors
  5. Container images use mutable tags - Pin by digest for production releases
  6. Interaction with pluginRegistry.openVSXURL undefined - Document the interaction to avoid user confusion

Design & Testing

  1. Server reconciler is overloaded - Manages 7 resources; consider splitting
  2. Duplicate PVC sync logic - Extract shared helper for create-or-expand pattern
  3. Missing test scenarios: PVC expansion, Job hash comparison, failure handling, expose reconciler

See the full review files for detailed analysis:

  • Standard review: findings on correctness, security, code quality
  • Deep review: design patterns, thoughtfulness, testing rigor
  • Impact review: operational readiness, supply chain, compatibility

Verdict: 🔄 Request Changes - The core implementation is solid, but the critical issues (#1-3) and operational gaps (#4-14) should be addressed before merge.

svor and others added 3 commits June 12, 2026 16:16
Co-authored-by: Anatolii Bazko <abazko@redhat.com>
Co-authored-by: Anatolii Bazko <abazko@redhat.com>
Co-authored-by: Anatolii Bazko <abazko@redhat.com>
@svor svor changed the title feat: Manage OpenVSX as Dev Spaces operand feat: Manage OpenVSX as Che operand Jun 12, 2026
When OpenVSX.Enable=true, the managed operand provides the registry.
Disable the embedded OpenVSX in the plugin registry and override
the dashboard's OpenVSX URL with Status.OpenVSXURL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants