Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions api/core/v1beta1/site_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,26 @@ type InternalPackageManagerSpec struct {
}

type InternalConnectSpec struct {
// Enabled controls whether Connect is deployed. Defaults to true if not specified.
// Set to false to explicitly disable Connect deployment.
//
// WARNING: Disabling Connect (setting Enabled=false) is a DESTRUCTIVE operation that
// permanently deletes all Connect resources including:
// - The Connect database and all its data
// - All secrets (database credentials, provisioning keys, etc.)
// - Persistent volumes and claims
// - All deployed Kubernetes resources (deployments, services, ingress, etc.)
//
// Re-enabling Connect after disabling it will start completely fresh with:
// - A new, empty database
// - New secrets and credentials
// - No content or configuration from the previous deployment
//
// This is intentional behavior, not a bug. Only disable Connect if you intend to
// permanently destroy the Connect instance and all its data.
// +optional
Enabled *bool `json:"enabled,omitempty"`

License product.LicenseSpec `json:"license,omitempty"`

Volume *product.VolumeSpec `json:"volume,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions api/core/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions config/crd/bases/core.posit.team_sites.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,26 @@ spec:
domainPrefix:
default: connect
type: string
enabled:
description: |-
Enabled controls whether Connect is deployed. Defaults to true if not specified.
Set to false to explicitly disable Connect deployment.

WARNING: Disabling Connect (setting Enabled=false) is a DESTRUCTIVE operation that
permanently deletes all Connect resources including:
- The Connect database and all its data
- All secrets (database credentials, provisioning keys, etc.)
- Persistent volumes and claims
- All deployed Kubernetes resources (deployments, services, ingress, etc.)

Re-enabling Connect after disabling it will start completely fresh with:
- A new, empty database
- New secrets and credentials
- No content or configuration from the previous deployment

This is intentional behavior, not a bug. Only disable Connect if you intend to
permanently destroy the Connect instance and all its data.
type: boolean
experimentalFeatures:
properties:
chronicleSidecarProductApiKeyEnabled:
Expand Down
20 changes: 20 additions & 0 deletions dist/chart/templates/crd/core.posit.team_sites.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,26 @@ spec:
domainPrefix:
default: connect
type: string
enabled:
description: |-
Enabled controls whether Connect is deployed. Defaults to true if not specified.
Set to false to explicitly disable Connect deployment.

WARNING: Disabling Connect (setting Enabled=false) is a DESTRUCTIVE operation that
permanently deletes all Connect resources including:
- The Connect database and all its data
- All secrets (database credentials, provisioning keys, etc.)
- Persistent volumes and claims
- All deployed Kubernetes resources (deployments, services, ingress, etc.)

Re-enabling Connect after disabling it will start completely fresh with:
- A new, empty database
- New secrets and credentials
- No content or configuration from the previous deployment

This is intentional behavior, not a bug. Only disable Connect if you intend to
permanently destroy the Connect instance and all its data.
type: boolean
experimentalFeatures:
properties:
chronicleSidecarProductApiKeyEnabled:
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ These types are used within the Site CRD for product configuration.

| Field | Type | Description |
|-------|------|-------------|
| `.enabled` | `*bool` | Enable/disable Connect deployment (default: true). **WARNING:** Setting to `false` permanently deletes all Connect data including the database, secrets, and volumes. Cannot be reversed. See [Connect Configuration Guide](guides/connect-configuration.md#enablingdisabling-connect) for details. |
| `.license` | `LicenseSpec` | License configuration |
| `.volume` | `*VolumeSpec` | Data volume |
| `.nodeSelector` | `map[string]string` | Node selector |
Expand Down
59 changes: 59 additions & 0 deletions docs/guides/connect-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ This comprehensive guide covers all configuration options for Posit Connect when

1. [Overview](#overview)
2. [Basic Configuration](#basic-configuration)
- [Enabling/Disabling Connect](#enablingdisabling-connect)
- [Image Configuration](#image-configuration)
- [Resource Scaling](#resource-scaling)
- [Domain and Ingress](#domain-and-ingress)
3. [Authentication Configuration](#authentication-configuration)
4. [Database Configuration](#database-configuration)
5. [Off-Host Execution / Kubernetes Launcher](#off-host-execution--kubernetes-launcher)
Expand Down Expand Up @@ -56,6 +60,61 @@ When using a Site resource, the Site controller generates and manages the Connec

## Basic Configuration

### Enabling/Disabling Connect

By default, Connect is enabled when specified in a Site configuration. You can explicitly control whether Connect is deployed:

```yaml
spec:
connect:
# Enable Connect deployment (default: true)
enabled: true
```

#### WARNING: Disabling Connect is Destructive

**Setting `enabled: false` is a PERMANENT, DESTRUCTIVE operation that cannot be reversed without complete data loss.**

When you disable Connect by setting `enabled: false`, the Team Operator:

1. **Deletes the Connect Custom Resource (CR)** from Kubernetes
2. **Triggers finalizers** that destroy all Connect resources, including:
- The Connect PostgreSQL database and **all its data** (published content, users, settings, schedules, etc.)
- All Kubernetes secrets (database credentials, provisioning keys, OAuth secrets)
- Persistent volumes and all stored content
- All Kubernetes deployments, services, ingress routes, and jobs

**This means:**

- All published content, applications, reports, and APIs are **permanently deleted**
- All user accounts, permissions, and settings are **permanently deleted**
- All content schedules, email subscriptions, and integrations are **permanently deleted**
- **Re-enabling Connect later will start with a completely fresh, empty instance** with no data from the previous deployment

**When to use `enabled: false`:**

- During initial cluster setup when you don't want Connect deployed yet
- When permanently decommissioning Connect and migrating to a different instance
- When you explicitly want to destroy all Connect data and start fresh

**Never use `enabled: false` for:**

- Temporarily stopping Connect (use `replicas: 0` instead to scale down without data loss)
- Maintenance windows (scale down replicas instead)
- Troubleshooting (use debug mode or check logs instead)

**Example of safely scaling down Connect temporarily:**

```yaml
spec:
connect:
# Scale down Connect pods without deleting data
replicas: 0
# Keep enabled: true (or omit it, as true is the default)
```

---

### Image Configuration

```yaml
Expand Down
5 changes: 5 additions & 0 deletions docs/guides/product-team-site-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ spec:
```yaml
spec:
connect:
# Enable/disable Connect deployment (default: true)
# WARNING: Setting enabled: false permanently deletes all Connect data!
# See the Connect Configuration Guide for details.
enabled: true

image: "ghcr.io/posit-dev/connect:ubuntu22-2024.10.0"
imagePullPolicy: IfNotPresent
replicas: 1
Expand Down
4 changes: 3 additions & 1 deletion flightdeck/html/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ func HomePage(site positcov1beta1.Site, config *internal.ServerConfig) Node {
"Manage your environments with integrated tools like JupyterLab, RStudio, VS Code and Positron. "+
"Self-service workspaces provide a secure solution for both on-premises and cloud deployments"),
),
If(!internal.IsEmptyStruct(site.Spec.Connect),
// Check Enabled field explicitly for Connect - when Enabled=false, Connect should not appear
// even if other fields like License or Image are set
If((site.Spec.Connect.Enabled == nil || *site.Spec.Connect.Enabled == true) && !internal.IsEmptyStruct(site.Spec.Connect),
productCard("/static/logo-connect.svg", "Posit Connect", site.Spec.Connect.DomainPrefix, connectBaseUrl,
"Share your interactive applications, dashboards, and reports built with R and Python. "+
"Manage access, and deliver real-time insights to your stakeholders."),
Expand Down
6 changes: 4 additions & 2 deletions flightdeck/html/siteconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ func SiteConfigTable(site positcov1beta1.Site) Node {
TBody(
If(!internal.IsEmptyStruct(site.Spec.Workbench),
SiteConfigTableRow("Workbench Image", site.Spec.Workbench.Image)),
If(!internal.IsEmptyStruct(site.Spec.Connect),
// Check Enabled field explicitly for Connect - when Enabled=false, Connect should not appear
If((site.Spec.Connect.Enabled == nil || *site.Spec.Connect.Enabled == true) && !internal.IsEmptyStruct(site.Spec.Connect),
SiteConfigTableRow("Connect Image", site.Spec.Connect.Image)),
If(!internal.IsEmptyStruct(site.Spec.PackageManager),
SiteConfigTableRow("Package Manager Image", site.Spec.PackageManager.Image)),
Expand All @@ -45,7 +46,8 @@ func SiteConfigBlock(site positcov1beta1.Site) Node {
if !internal.IsEmptyStruct(site.Spec.Workbench) {
productConfigs["Workbench"] = site.Spec.Workbench
}
if !internal.IsEmptyStruct(site.Spec.Connect) {
// Check Enabled field explicitly for Connect - when Enabled=false, Connect config should not appear
if (site.Spec.Connect.Enabled == nil || *site.Spec.Connect.Enabled == true) && !internal.IsEmptyStruct(site.Spec.Connect) {
productConfigs["Connect"] = site.Spec.Connect
}
if !internal.IsEmptyStruct(site.Spec.PackageManager) {
Expand Down
16 changes: 16 additions & 0 deletions internal/controller/core/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,22 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl.
return ctrl.Result{}, nil
}

// CleanupConnect is the finalizer that runs when a Connect CRD is deleted.
//
// WARNING: This function performs DESTRUCTIVE cleanup operations that permanently destroy:
// - The Connect database via db.CleanupDatabase (drops the database if configured to do so)
// - All secrets: provisioning keys, database password secrets, etc.
// - All Kubernetes resources: deployments, services, ingress, PVCs, configmaps, etc.
//
// This finalizer is automatically triggered when:
// 1. The Site CR is deleted (complete teardown)
// 2. Connect is disabled via Site.Spec.Connect.Enabled=false (user disables the product)
//
// When a user sets Enabled=false, the site controller calls cleanupConnect() which deletes
// the Connect CRD, triggering this finalizer. This results in complete data loss.
//
// Re-enabling Connect after disabling it will start fresh with a new database, new secrets,
// and no previous content. This is intentional behavior to ensure clean resource teardown.
func (r *ConnectReconciler) CleanupConnect(ctx context.Context, req ctrl.Request, c *positcov1beta1.Connect) (ctrl.Result, error) {
if err := r.cleanupDeployedService(ctx, req, c); err != nil {
return ctrl.Result{}, err
Expand Down
57 changes: 38 additions & 19 deletions internal/controller/core/site_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,11 @@ func (r *SiteReconciler) reconcileResources(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, err
}

if err := r.provisionFsxVolume(ctx, site, connectVolumeName, "connect", connectVolumeSize); err != nil {
return ctrl.Result{}, err
// Only provision Connect volume if Connect is enabled
if site.Spec.Connect.Enabled == nil || *site.Spec.Connect.Enabled == true {
if err := r.provisionFsxVolume(ctx, site, connectVolumeName, "connect", connectVolumeSize); err != nil {
return ctrl.Result{}, err
}
}

if err := r.provisionFsxVolume(ctx, site, devVolumeName, "workbench", connectVolumeSize); err != nil {
Expand Down Expand Up @@ -209,10 +212,13 @@ func (r *SiteReconciler) reconcileResources(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, err
}

connectStorageClassName = fmt.Sprintf("%s-nfs", connectVolumeName)
// Only provision Connect volume if Connect is enabled
if site.Spec.Connect.Enabled == nil || *site.Spec.Connect.Enabled == true {
connectStorageClassName = fmt.Sprintf("%s-nfs", connectVolumeName)

if err := r.provisionNfsVolume(ctx, site, connectVolumeName, "connect", connectStorageClassName, connectVolumeSize); err != nil {
return ctrl.Result{}, err
if err := r.provisionNfsVolume(ctx, site, connectVolumeName, "connect", connectStorageClassName, connectVolumeSize); err != nil {
return ctrl.Result{}, err
}
}

devStorageClassName = fmt.Sprintf("%s-nfs", devVolumeName)
Expand Down Expand Up @@ -296,20 +302,33 @@ func (r *SiteReconciler) reconcileResources(ctx context.Context, req ctrl.Reques
workbenchAdditionalVolumes = append(workbenchAdditionalVolumes, site.Spec.Workbench.AdditionalVolumes...)

// CONNECT
if err := r.reconcileConnect(
ctx,
req,
site,
dbUrl.Host,
sslMode,
connectVolumeName,
connectStorageClassName,
additionalVolumes,
packageManagerRepoUrl,
connectUrl,
); err != nil {
l.Error(err, "error reconciling connect")
return ctrl.Result{}, err
// When Enabled is nil or true, reconcile Connect (create/update resources)
// When Enabled is false, cleanup Connect (DESTRUCTIVE: deletes database, secrets, and all resources)
if site.Spec.Connect.Enabled == nil || *site.Spec.Connect.Enabled == true {
if err := r.reconcileConnect(
ctx,
req,
site,
dbUrl.Host,
sslMode,
connectVolumeName,
connectStorageClassName,
additionalVolumes,
packageManagerRepoUrl,
connectUrl,
); err != nil {
l.Error(err, "error reconciling connect")
return ctrl.Result{}, err
}
} else {
// Connect is disabled - clean up any existing Connect resources
// WARNING: This triggers permanent deletion of the Connect CRD, which causes
// the Connect finalizer to destroy the database, secrets, and all resources.
// See cleanupConnect() and CleanupConnect() for details.
if err := r.cleanupConnect(ctx, req, l); err != nil {
l.Error(err, "error cleaning up connect resources")
return ctrl.Result{}, err
}
}

// PACKAGE MANAGER
Expand Down
29 changes: 29 additions & 0 deletions internal/controller/core/site_controller_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"context"
"fmt"

"github.com/go-logr/logr"
"github.com/posit-dev/team-operator/api/core/v1beta1"
"github.com/posit-dev/team-operator/api/product"
"github.com/posit-dev/team-operator/internal"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func (r *SiteReconciler) reconcileConnect(
Expand Down Expand Up @@ -249,3 +251,30 @@ func (r *SiteReconciler) reconcileConnect(
}
return nil
}

// cleanupConnect deletes the Connect CRD when Connect is disabled (Enabled=false).
//
// WARNING: This is a DESTRUCTIVE operation. Deleting the Connect CRD triggers the Connect
// finalizer (CleanupConnect in connect_controller.go) which permanently destroys:
// - The Connect database and all its data
// - All secrets (database credentials, provisioning keys, etc.)
// - Persistent volumes and claims
// - All deployed Kubernetes resources
//
// This means that disabling Connect via Site.Spec.Connect.Enabled=false is a one-way
// operation that results in complete data loss. Re-enabling Connect will start fresh
// with a new database and no previous content or configuration.
//
// This behavior is intentional to ensure clean teardown of Connect resources when
// a user explicitly disables the product.
func (r *SiteReconciler) cleanupConnect(ctx context.Context, req controllerruntime.Request, l logr.Logger) error {
l = l.WithValues("event", "cleanup-connect")

// Delete Connect CRD if it exists
connectKey := client.ObjectKey{Name: req.Name, Namespace: req.Namespace}
if err := internal.BasicDelete(ctx, r, l, connectKey, &v1beta1.Connect{}); err != nil {
return err
}

return nil
}
Loading
Loading