Skip to content

feat(control-plane): provision gateway databases from an admin Secret - #286

Closed
jsell-rh wants to merge 6 commits into
mainfrom
feat/controller-database-secret
Closed

jsell-rh wants to merge 6 commits into
mainfrom
feat/controller-database-secret

Conversation

@jsell-rh

@jsell-rh jsell-rh commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

GATEWAY_DATABASE_ADMIN_SECRET_NAME selects a PostgreSQL admin Secret in HYPERSHELL_NAMESPACE. The controller creates one SQL database and login role per gateway on that server. It skips the ManagedDatabase watch, database lookup, and CNPG startup check. The unset path keeps its current behavior.

The override requires verify-full and a PEM CA bundle. The gateway receives its own credentials and the public CA through a read-only mount. The controller grants the role membership needed for a non-superuser admin account. Failed cleanup queries and SQL operations return errors to the live retry queue and record an IncompleteFinalization warning Event.

The specification now has named requirements and Given/When/Then scenarios. A Kustomize component shows how External Secrets Operator can synchronize a secret-manager entry. The documentation covers credential updates, CA updates, and manual cleanup after a controller restart.

The API, schema, SDKs, and migrations do not change. The API still assigns database_id and creates or selects its current ManagedDatabase records. The controller ignores and retains these records in override mode. This change does not migrate existing gateway data.

Validation:

  • All control-plane unit tests passed.
  • Standalone PostgreSQL 18 tests passed with a non-superuser admin: provisioning, tenant TLS, wrong hostname and untrusted CA rejection, stable tenant credentials, admin password update, missing Secret recovery, and failed cleanup followed by retry.
  • Control-plane lint and repository policy checks passed.
  • Shell syntax, ShellCheck, Kustomize rendering, and the pinned ESO chart API fields passed validation.
  • CI includes a secret-override case with ESO synchronization and deployed API/CLI assertions. Local Kind work was stopped at the user's request. The complete cluster test and live cloud secret-manager authentication remain unverified.

Operational limits: the cleanup retry queue does not survive a controller restart. Admin password updates are read on each SQL operation. CA changes for running gateways need the tenant CA update and deployment restart described in the runbook.

@coderabbitai

coderabbitai Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: e8639635-f162-4093-9941-0af0e33ca7b8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@jsell-rh
jsell-rh marked this pull request as ready for review September 15, 2026 19:23
@amber-review-bot

amber-review-bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

amber-review-bot

This comment was marked as outdated.

@jsell-rh jsell-rh changed the title feat(control-plane): use a configured Secret for gateway databases feat(control-plane): provision gateway databases from an admin Secret Sep 15, 2026
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

HyperShell environment updating to commit 4e8c195

Updating the ephemeral OpenShift environment to commit 4e8c195. The
environment may not be fully responsive during the update. This comment will
update in place once the environment is ready.

Fact Value
Namespaces Platform: hypershell-ci-pr-286 Keycloak: hypershell-ci-pr-286-keycloak
OpenShift console https://console-openshift-console.apps.rosa.hysh-aws-01.c6uk.p3.openshiftapps.com
API https://hypershell-api-hypershell-ci-pr-286.apps.rosa.hysh-aws-01.c6uk.p3.openshiftapps.com
Web console https://hypershell-web-console-hypershell-ci-pr-286.apps.rosa.hysh-aws-01.c6uk.p3.openshiftapps.com

Log in through the web console with your GitHub account (you must be a member of
the configured organization or on its allowlist). The environment is time-boxed
and refreshed on every new commit.

CLI access
oc login --server=https://api.hysh-aws-01.c6uk.p3.openshiftapps.com:443 --web

amber-review-bot

This comment was marked as outdated.

@amber-review-bot amber-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

The controller-local admin-Secret provisioning path remains well-scoped and defensively coded: the admin Secret name/namespace are DNS-validated at load, verify-full plus a valid PEM CA is enforced before any SQL runs, cleanup returns catalog/DDL errors to the retry queue instead of masking them, and no admin credentials or secret values leak into logs, errors, or the tenant Secret. The only maintainer-action items are cross-PR design/ordering decisions, not defects in this change.

Since the previously reviewed commit, the only delta is a merge from main; the feature code under internal/config, internal/gateway, cmd/hypershell-controller, and manifests/gateway/deployment.yaml is unchanged. Re-verification against the current tree confirms the prior assessment still holds.

Strengths

  • GATEWAY_DATABASE_ADMIN_SECRET_NAME is validated at load time (IsDNS1123Subdomain for the name, IsDNS1123Label for HYPERSHELL_NAMESPACE) so an invalid reference fails fast before any read (internal/config/config.go:120-126).
  • The override is a hardened production path: readExternalAdminCredentials forces verify-full, rejects any weaker sslmode, requires a parseable PEM sslrootcert, and validates the port range before opening a connection (internal/gateway/external_db.go:148-172). The legacy API-reference path keeps its reserved-prefix / fixed-name restriction.
  • TLS is verified end to end: the CA is projected read-only into the gateway pod at /etc/openshell-db/ca.crt, mounting only the public sslrootcert key (not admin credentials), and the tenant DSN references that path (external_db.go:636-654, manifests/gateway/deployment.yaml).
  • Cleanup correctness: deleteExternalSQLResources returns existence-query errors instead of treating a failed probe as "absent"; in override mode Delete returns the error to the live retry queue and otherwise records an orphan warning naming the gateway and DB without credential values (external_db.go:48-60,595-634).
  • No silent fallback: failures to read or validate the admin Secret abort provisioning and cleanup.

Test Diff Scrutiny

The modified pre-existing test assertions are additive, not weakened. managedDatabaseWatchEligible(..., "") in main_test.go adds the new parameter with an empty value to preserve original behavior, with a companion test for the new branch. external_db_test.go, manifests_test.go, config_test.go, and reconciler_test.go only add new cases. No prior guarantee (accepted -> rejected, optional -> required, success -> error) was silently flipped.

Cross-PR coordination

Two other open pull requests make design or structural choices that must be reconciled with this change before both can land; these are decisions/ordering items, not mechanical merge conflicts:

  • A competing spec pull request proposes the opposite, intentionally breaking resolution of the same "controller-local gateway database" problem: it deletes specs/platform/openshell-gateway-database-external.spec.md (the exact file this PR extends), removes the ManagedDatabase entity from data-model.spec.md, and drops database_id from the Gateway API. This PR is built on the opposite premise - it keeps ManagedDatabase/database_id, has the API still assign them, and has the controller ignore and retain those records. Maintainers must decide which model is the target design and the merge order: if this PR lands first, the other will delete the section added here; if the other lands first, this PR's spec extension and its ManagedDatabase-preserving code path have no data model to attach to.
  • A pull request that adopts the upstream OpenShell Helm chart for gateway deployments deletes components/control-plane/manifests/gateway/deployment.yaml, the same manifest this PR edits to add the database-ca volume and read-only mount that the verify-full tenant TLS feature depends on. If that pull request merges after this one, the CA projection is silently lost unless it is re-expressed in the Helm chart values. Maintainers should decide merge order and who ports the CA projection into the Helm path.

Previous concerns

The prior Amber review (review 5215111487) raised no blocking, critical, or major code-level findings; its only maintainer item was the cross-PR design decision about the competing controller-local-database spec and the Helm-chart manifest deletion. That item is still present and is re-stated above: the competing pull request still deletes the external-database spec file this PR extends and still removes the ManagedDatabase entity and database_id field that this PR's design retains, and the Helm-adoption pull request still deletes the gateway deployment.yaml this PR modifies for the CA mount.

Findings Summary

No blocking, critical, or major code defects were found. The only maintainer-action items are the cross-PR design/ordering decisions noted above.

Convention Checklist

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
errors.IsNotFound handled for 404 scenarios Pass
No secrets in logs or error messages Pass
Input validated (Secret name / namespace DNS format, port, PEM CA) Pass
SecurityContext / read-only mount for projected CA Pass
Reconcile pattern, no create-or-skip Pass
No silent fallback on error paths Pass
Spec updated alongside behavior change Pass
Test diff scrutiny (no silently removed guarantees) Pass
Conventional commit messages Pass

@jsell-rh

Copy link
Copy Markdown
Collaborator Author

@rh-amarin will fold this into his work

rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 16, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback, no API-side database inventory, and
TLS is verify-full everywhere with no downgrade path.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- Tenant Secret openshell-gateway-db-credentials now carries sslmode and
  sslrootcert; the gateway Deployment mounts the CA at
  /etc/openshell-db/ca.crt and both admin and gateway connections verify
  the server certificate and hostname.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is now the tenant Secret's
  sslmode=verify-full and non-empty sslrootcert.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 16, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback, no API-side database inventory, and
TLS is verify-full everywhere with no downgrade path.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- Tenant Secret openshell-gateway-db-credentials now carries sslmode and
  sslrootcert; the gateway Deployment mounts the CA at
  /etc/openshell-db/ca.crt and both admin and gateway connections verify
  the server certificate and hostname.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is now the tenant Secret's
  sslmode=verify-full and non-empty sslrootcert.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 16, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback, no API-side database inventory, and
TLS is verify-full everywhere with no downgrade path.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- Tenant Secret openshell-gateway-db-credentials now carries sslmode and
  sslrootcert; the gateway Deployment mounts the CA at
  /etc/openshell-db/ca.crt and both admin and gateway connections verify
  the server certificate and hostname.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is now the tenant Secret's
  sslmode=verify-full and non-empty sslrootcert.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rh-amarin
rh-amarin marked this pull request as draft September 16, 2026 20:38
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 17, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback, no API-side database inventory, and
TLS is verify-full everywhere with no downgrade path.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- Tenant Secret openshell-gateway-db-credentials now carries sslmode and
  sslrootcert; the gateway Deployment mounts the CA at
  /etc/openshell-db/ca.crt and both admin and gateway connections verify
  the server certificate and hostname.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is now the tenant Secret's
  sslmode=verify-full and non-empty sslrootcert.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jsell-rh

Copy link
Copy Markdown
Collaborator Author

closing in-favor of #300

@jsell-rh jsell-rh closed this Sep 17, 2026
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 18, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback, no API-side database inventory, and
TLS is verify-full everywhere with no downgrade path.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- Tenant Secret openshell-gateway-db-credentials now carries sslmode and
  sslrootcert; the gateway Deployment mounts the CA at
  /etc/openshell-db/ca.crt and both admin and gateway connections verify
  the server certificate and hostname.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is now the tenant Secret's
  sslmode=verify-full and non-empty sslrootcert.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 18, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback, no API-side database inventory, and
TLS is verify-full everywhere with no downgrade path.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- Tenant Secret openshell-gateway-db-credentials now carries sslmode and
  sslrootcert; the gateway Deployment mounts the CA at
  /etc/openshell-db/ca.crt and both admin and gateway connections verify
  the server certificate and hostname.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is now the tenant Secret's
  sslmode=verify-full and non-empty sslrootcert.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rh-amarin pushed a commit that referenced this pull request Sep 20, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR #286
(jsell-rh): no dual-provider fallback and no API-side database inventory.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- The admin connection is always sslmode=verify-full: the control plane
  mounts the CA itself and verifies the server certificate and hostname.
- The tenant Secret openshell-gateway-db-credentials carries sslmode=require
  and no sslrootcert. The gateway workload is deployed by the upstream
  OpenShell Helm chart, which reads only this Secret's uri key
  (server.externalDbSecret) and has no mechanism to mount a database CA into
  the gateway pod, so the gateway connection is encrypted but not
  certificate-verified. See specs/platform/openshell-gateway-helm-adoption.spec.md
  § Gap: No Database CA Mount.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).
- GatewayReconciler takes the DatabaseConfig alongside the Helm client added
  by PR #194 and refuses to start without an admin credentials directory.

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Dropped the ManagedDatabase tombstone-replay branch from the gRPC RBAC
  stream interceptor and its test.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.
- Dropped the managed-database-status widget from the default layout, added
  it to REMOVED_WIDGET_TYPES so saved grids shed it, widened the cluster
  regions donut back across columns 2-3 and bumped the layout persistence
  key to v42.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is the tenant Secret's
  sslmode=require, a uri that requests TLS, and the absence of an
  sslrootcert key.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.
- Dashboard users must reset their layout or clear the v41 localStorage key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rh-amarin pushed a commit that referenced this pull request Sep 21, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR #286
(jsell-rh): no dual-provider fallback and no API-side database inventory.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- The admin connection is always sslmode=verify-full: the control plane
  mounts the CA itself and verifies the server certificate and hostname.
- The tenant Secret openshell-gateway-db-credentials carries sslmode=require
  and no sslrootcert. The gateway workload is deployed by the upstream
  OpenShell Helm chart, which reads only this Secret's uri key
  (server.externalDbSecret) and has no mechanism to mount a database CA into
  the gateway pod, so the gateway connection is encrypted but not
  certificate-verified. See specs/platform/openshell-gateway-helm-adoption.spec.md
  § Gap: No Database CA Mount.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).
- GatewayReconciler takes the DatabaseConfig alongside the Helm client added
  by PR #194 and refuses to start without an admin credentials directory.

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Dropped the ManagedDatabase tombstone-replay branch from the gRPC RBAC
  stream interceptor and its test.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.
- Dropped the managed-database-status widget from the default layout, added
  it to REMOVED_WIDGET_TYPES so saved grids shed it, widened the cluster
  regions donut back across columns 2-3 and bumped the layout persistence
  key to v42.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is the tenant Secret's
  sslmode=require, a uri that requests TLS, and the absence of an
  sslrootcert key.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.
- Dashboard users must reset their layout or clear the v41 localStorage key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 21, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback and no API-side database inventory.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- The admin connection is always sslmode=verify-full: the control plane
  mounts the CA itself and verifies the server certificate and hostname.
- The tenant Secret openshell-gateway-db-credentials carries sslmode=require
  and no sslrootcert. The gateway workload is deployed by the upstream
  OpenShell Helm chart, which reads only this Secret's uri key
  (server.externalDbSecret) and has no mechanism to mount a database CA into
  the gateway pod, so the gateway connection is encrypted but not
  certificate-verified. See specs/platform/openshell-gateway-helm-adoption.spec.md
  § Gap: No Database CA Mount.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).
- GatewayReconciler takes the DatabaseConfig alongside the Helm client added
  by PR openshift-online#194 and refuses to start without an admin credentials directory.

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Dropped the ManagedDatabase tombstone-replay branch from the gRPC RBAC
  stream interceptor and its test.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.
- Dropped the managed-database-status widget from the default layout, added
  it to REMOVED_WIDGET_TYPES so saved grids shed it, widened the cluster
  regions donut back across columns 2-3 and bumped the layout persistence
  key to v42.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is the tenant Secret's
  sslmode=require, a uri that requests TLS, and the absence of an
  sslrootcert key.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.
- Dashboard users must reset their layout or clear the v41 localStorage key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 21, 2026
Remove the ManagedDatabase resource and Gateway.database_id entirely and
replace them with a single PostgreSQL admin credential Secret mounted into
the control plane. Incorporates and simplifies the idea from PR openshift-online#286
(jsell-rh): no dual-provider fallback and no API-side database inventory.

Control plane:
- New internal/gateway/database.go reads the admin Secret from files at
  GATEWAY_DATABASE_ADMIN_DIR (default /etc/hypershell/gateway-database):
  host, port, user, password, sslrootcert required; dbname optional;
  sslmode optional but must be verify-full if present. Re-read on every
  operation so a rotated admin password needs no controller restart.
- gateway.ValidateAdminCredentialsDir is a hard startup precondition: the
  controller refuses to start on a missing/malformed Secret, without
  connecting to the server. Reachability and privileges are checked at
  reconcile time with retries.
- Per gateway: role and database gw_<id>, GRANT gw_<id> TO <admin user> so
  a non-superuser admin with CREATEDB+CREATEROLE can own the database,
  REVOKE CONNECT FROM PUBLIC, GRANT CONNECT. Password reused from the
  existing tenant Secret or generated with crypto/rand; ALTER ROLE only as
  repair, never rotation.
- The admin connection is always sslmode=verify-full: the control plane
  mounts the CA itself and verifies the server certificate and hostname.
- The tenant Secret openshell-gateway-db-credentials carries sslmode=require
  and no sslrootcert. The gateway workload is deployed by the upstream
  OpenShell Helm chart, which reads only this Secret's uri key
  (server.externalDbSecret) and has no mechanism to mount a database CA into
  the gateway pod, so the gateway connection is encrypted but not
  certificate-verified. See specs/platform/openshell-gateway-helm-adoption.spec.md
  § Gap: No Database CA Mount.
- A failed cleanup on gateway delete returns an error to the retry queue
  and records a PostgreSQLDatabase IncompleteFinalization Event instead of
  logging and moving on.
- Removed the ManagedDatabase watch, reconciler and gRPC service consumer
  entirely (watcher.go, reconciler.go).
- GatewayReconciler takes the DatabaseConfig alongside the Helm client added
  by PR openshift-online#194 and refuses to start without an admin credentials directory.

API server, CLI, SDKs:
- Deleted the managedDatabases plugin, its OpenAPI/proto/gRPC surface, and
  the CLI managedDatabase(s) commands.
- Removed database_id from Gateway (model, OpenAPI, proto field reserved
  by number and name, presenters, handlers, CLI, both SDKs) and the
  gateway placement code that assigned it.
- Dropped the ManagedDatabase tombstone-replay branch from the gRPC RBAC
  stream interceptor and its test.
- Migrations: drop gateways.database_id and the managed_databases table.

UI:
- Removed the operational dashboard's database inventory widget and
  metric, the BFF's managed-database Prometheus queries, and the gateway
  detail page's database ID field.
- Dropped the managed-database-status widget from the default layout, added
  it to REMOVED_WIDGET_TYPES so saved grids shed it, widened the cluster
  regions donut back across columns 2-3 and bumped the layout persistence
  key to v42.

Deploy, scripts, CI:
- New scripts/gen-postgres-tls.sh shared by kind and OpenShift dev
  environments to mint a CA and serve the stand-in PostgreSQL over TLS.
- Both dev drivers create the hypershell-gateway-database-admin Secret
  with sslmode=verify-full before deploying the controller; the
  hypershell-managed-db-* credentials namespaces and openshell-db seed
  are gone.
- New optional Kustomize component
  deploy/components/gateway-database-admin-secret shows External Secrets
  Operator syncing the admin Secret from a secret manager.
- e2e proof of database provisioning is the tenant Secret's
  sslmode=require, a uri that requests TLS, and the absence of an
  sslrootcert key.

Specs and docs rewritten to match: specs/platform/openshell-gateway-database.spec.md
is the single source of truth for the new design; platform-inventory,
data-model, rbac-enforcement, naming-multitenancy and the web-console specs
updated to remove the ManagedDatabase surface.

Upgrade notes:
- The migrations are destructive: existing gateway database_id values and
  the managed_databases table are dropped with no backfill.
- Existing openshell-db-* namespaces lose their GC exclusion and become
  eligible for garbage collection.
- OpenShift dev environments need make openshift-down && make openshift-up
  because the credentials project and TLS Secret shape changed.
- Dashboard users must reset their layout or clear the v41 localStorage key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <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