Skip to content
Merged
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
5 changes: 5 additions & 0 deletions api/core/v1beta1/connect_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ type ConnectSpec struct {
// but can also be useful on occasion
Sleep bool `json:"sleep,omitempty"`

// Suspended indicates Connect should not run serving resources (Deployment, Service, Ingress)
// but should preserve data resources (PVC, database, secrets). Set by the Site controller.
// +optional
Suspended *bool `json:"suspended,omitempty"`

SessionImage string `json:"sessionImage,omitempty"`

// AwsAccountId is the account Id for this AWS Account. It is used to create EKS-to-IAM annotations
Expand Down
15 changes: 15 additions & 0 deletions api/core/v1beta1/site_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,21 @@ type InternalPackageManagerSpec struct {
}

type InternalConnectSpec struct {
// Enabled controls whether Connect is running. Defaults to true.
// Setting to false suspends Connect: stops pods and removes ingress/service,
// but preserves PVC, database, and secrets so data is retained.
// Re-enabling restores full service without data loss.
// +kubebuilder:default=true
// +optional
Enabled *bool `json:"enabled,omitempty"`

// Teardown permanently destroys all Connect resources including the database,
// secrets, and persistent volume claim. Only takes effect when Enabled is false.
// Re-enabling after teardown starts fresh with a new empty database.
// +kubebuilder:default=false
// +optional
Teardown *bool `json:"teardown,omitempty"`

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

Volume *product.VolumeSpec `json:"volume,omitempty"`
Expand Down
15 changes: 15 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.

9 changes: 9 additions & 0 deletions client-go/applyconfiguration/core/v1beta1/connectspec.go

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

18 changes: 18 additions & 0 deletions client-go/applyconfiguration/core/v1beta1/internalconnectspec.go

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

5 changes: 5 additions & 0 deletions config/crd/bases/core.posit.team_connects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7332,6 +7332,11 @@ spec:
Sleep puts the service to sleep... so you can debug a crash looping container / etc. It is an ugly escape hatch,
but can also be useful on occasion
type: boolean
suspended:
description: |-
Suspended indicates Connect should not run serving resources (Deployment, Service, Ingress)
but should preserve data resources (PVC, database, secrets). Set by the Site controller.
type: boolean
url:
type: string
volume:
Expand Down
15 changes: 15 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,14 @@ spec:
domainPrefix:
default: connect
type: string
enabled:
default: true
description: |-
Enabled controls whether Connect is running. Defaults to true.
Setting to false suspends Connect: stops pods and removes ingress/service,
but preserves PVC, database, and secrets so data is retained.
Re-enabling restores full service without data loss.
type: boolean
experimentalFeatures:
properties:
chronicleSidecarProductApiKeyEnabled:
Expand Down Expand Up @@ -433,6 +441,13 @@ spec:
type: integer
sessionImage:
type: string
teardown:
default: false
description: |-
Teardown permanently destroys all Connect resources including the database,
secrets, and persistent volume claim. Only takes effect when Enabled is false.
Re-enabling after teardown starts fresh with a new empty database.
type: boolean
volume:
description: VolumeSpec is a specification for a PersistentVolumeClaim
to be created (and/or mounted)
Expand Down
5 changes: 5 additions & 0 deletions dist/chart/templates/crd/core.posit.team_connects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7353,6 +7353,11 @@ spec:
Sleep puts the service to sleep... so you can debug a crash looping container / etc. It is an ugly escape hatch,
but can also be useful on occasion
type: boolean
suspended:
description: |-
Suspended indicates Connect should not run serving resources (Deployment, Service, Ingress)
but should preserve data resources (PVC, database, secrets). Set by the Site controller.
type: boolean
url:
type: string
volume:
Expand Down
15 changes: 15 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,14 @@ spec:
domainPrefix:
default: connect
type: string
enabled:
default: true
description: |-
Enabled controls whether Connect is running. Defaults to true.
Setting to false suspends Connect: stops pods and removes ingress/service,
but preserves PVC, database, and secrets so data is retained.
Re-enabling restores full service without data loss.
type: boolean
experimentalFeatures:
properties:
chronicleSidecarProductApiKeyEnabled:
Expand Down Expand Up @@ -454,6 +462,13 @@ spec:
type: integer
sessionImage:
type: string
teardown:
default: false
description: |-
Teardown permanently destroys all Connect resources including the database,
secrets, and persistent volume claim. Only takes effect when Enabled is false.
Re-enabling after teardown starts fresh with a new empty database.
type: boolean
volume:
description: VolumeSpec is a specification for a PersistentVolumeClaim
to be created (and/or mounted)
Expand Down
2 changes: 2 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,8 @@ These types are used within the Site CRD for product configuration.

| Field | Type | Description |
|-------|------|-------------|
| `.enabled` | `*bool` | Controls whether Connect is running (default: true). Setting to `false` suspends Connect: stops pods and removes ingress/service, but preserves PVC, database, and secrets. Re-enabling restores full service without data loss. See [Connect Configuration Guide](guides/connect-configuration.md#enablingdisabling-connect). |
| `.teardown` | `*bool` | When `true` and `enabled` is `false`, permanently destroys all Connect resources including the database, secrets, and PVC. Re-enabling after teardown starts fresh with an empty database. Defaults to `false`. |
| `.license` | `LicenseSpec` | License configuration |
| `.volume` | `*VolumeSpec` | Data volume |
| `.nodeSelector` | `map[string]string` | Node selector |
Expand Down
55 changes: 55 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,57 @@ When using a Site resource, the Site controller generates and manages the Connec

## Basic Configuration

### Enabling/Disabling Connect

Connect can be suspended or permanently torn down using the `enabled` and `teardown` fields.

#### Suspending Connect (non-destructive)

Setting `enabled: false` suspends Connect: the Deployment, Service, and Ingress are removed, but the PVC, database, and secrets are preserved. Re-enabling restores full service with all existing data intact.

```yaml
spec:
connect:
enabled: false # suspend — data is preserved
```

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

- Customer does not have a Connect license yet — deploy the site without Connect and enable it once a license is purchased
- Temporarily pause Connect during a maintenance window or cost-saving period
- Stop Connect while retaining all content and user data for a possible return

**Re-enabling Connect** after a suspend is as simple as removing the field or setting it back to `true`:

```yaml
spec:
connect:
enabled: true # or omit the field entirely — defaults to true
```

#### Tearing down Connect (destructive)

To permanently destroy all Connect resources — including the database, secrets, and PVC — set both `enabled: false` and `teardown: true`:

```yaml
spec:
connect:
enabled: false
teardown: true # DESTRUCTIVE: deletes database, secrets, and PVC
```

**This is irreversible.** Re-enabling Connect after a teardown starts completely fresh with a new empty database and no prior content or configuration.

**When to use `teardown: true`:**

- Permanently decommissioning Connect with no intent to restore data
- Reclaiming cluster storage after migrating to a different Connect instance
- Explicitly wiping Connect to start fresh

> **Note:** `teardown: true` has no effect while `enabled` is `true` or unset. You must set `enabled: false` first.

---

### Image Configuration

```yaml
Expand Down
6 changes: 6 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,12 @@ spec:
```yaml
spec:
connect:
# Enable/disable Connect deployment (default: true).
# Setting enabled: false suspends Connect (preserves data).
# Use teardown: true to permanently delete 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
43 changes: 43 additions & 0 deletions internal/controller/core/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (r *ConnectReconciler) ReconcileConnect(ctx context.Context, req ctrl.Reque
"product", "connect",
)

// If suspended, clean up serving resources (Deployment/Service/Ingress) but preserve data
if c.Spec.Suspended != nil && *c.Spec.Suspended {
return r.suspendDeployedService(ctx, req, c)
}

// create database
secretKey := "pub-db-password"

Expand Down Expand Up @@ -840,6 +845,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 teardown is requested via Site.Spec.Connect.Teardown=true (when Enabled=false)
//
// When a user sets Teardown=true, the site controller calls cleanupConnect() which deletes
// the Connect CRD, triggering this finalizer. This results in complete data loss.
//
// Re-enabling Connect after teardown 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 All @@ -857,6 +878,28 @@ func (r *ConnectReconciler) CleanupConnect(ctx context.Context, req ctrl.Request
return ctrl.Result{}, nil
}

func (r *ConnectReconciler) suspendDeployedService(ctx context.Context, req ctrl.Request, c *positcov1beta1.Connect) (ctrl.Result, error) {
l := r.GetLogger(ctx).WithValues(
"event", "suspend-service",
"product", "connect",
)

key := client.ObjectKey{Name: c.ComponentName(), Namespace: req.Namespace}

if err := internal.BasicDelete(ctx, r, l, key, &networkingv1.Ingress{}); err != nil {
return ctrl.Result{}, err
}
if err := internal.BasicDelete(ctx, r, l, key, &corev1.Service{}); err != nil {
return ctrl.Result{}, err
}
if err := internal.BasicDelete(ctx, r, l, key, &v1.Deployment{}); err != nil {
return ctrl.Result{}, err
}

l.Info("Connect service suspended successfully")
return ctrl.Result{}, nil
}

func (r *ConnectReconciler) cleanupDeployedService(ctx context.Context, req ctrl.Request, c *positcov1beta1.Connect) error {
l := r.GetLogger(ctx).WithValues(
"event", "cleanup-service",
Expand Down
Loading