Skip to content

Apply .posit/publish config Connect-side settings: post-deploy hook + rsconnect sync #831

Description

@mconflitti-pbc

Summary

Follow-up to #830. That PR made rsconnect-python read/write Posit Publisher's
.posit/publish TOML, drive bundling from a config's files, and propagate
integration_requests into manifest.json. What it does not do is apply the
config's Connect-side settings — the fields that Publisher applies through the
Connect API after upload rather than through manifest.json.

Today those fields (description, connect.runtime.*, connect.access.*,
connect.kubernetes.*, environment, secrets) are round-tripped and preserved
in the config, but a rsconnect deploy/redeploy from a Publisher-authored config
silently does not apply them to the content. This issue tracks closing that gap
in two pieces:

  1. A post-deploy hook that applies the config's Connect-side settings after a
    successful deploy (mirroring Publisher).
  2. A rsconnect sync command that applies the current TOML settings to
    already-deployed content ad hoc, without rebuilding/redeploying a bundle —
    for when a user edits the config and just wants Connect updated.

Where each config field is applied (reference: Posit Publisher)

Publisher builds the manifest from the config, then makes two kinds of API calls.
Verified in the publisher repo:
extensions/vscode/src/bundler/{manifestFromConfig,connectContentFromConfig}.ts
and extensions/vscode/src/publish/{connectPublish,publishShared}.ts.

Config field Channel Status in rsconnect after #830
type, entrypoint, python, quarto, r, jupyter, has_parameters manifest.json ✅ (from inspection)
integration_requests manifest.json ✅ propagated in #830
title PATCH /v1/content/{guid} ✅ (already applied via executor)
description PATCH /v1/content/{guid} ❌ not applied
connect.runtime.* PATCH /v1/content/{guid} ❌ not applied
connect.access.* PATCH /v1/content/{guid} ❌ not applied
connect.kubernetes.* PATCH /v1/content/{guid} ❌ not applied
environment (env-var map) env-var API (setEnvVars) ⚠️ CLI path exists, not sourced from config
secrets env-var API (setEnvVars, merged; secrets override) ❌ not applied

Fields to apply via PATCH /v1/content/{guid}

Built by Publisher's connectContentFromConfig:

  • metadata: title, description (Connect rejects description > 4096 chars)
  • runtime (connect.runtime.*): connection_timeout, read_timeout,
    init_timeout, idle_timeout, max_processes, min_processes,
    max_conns_per_process, load_factor
  • access (connect.access.*): run_as, run_as_current_user
  • kubernetes (connect.kubernetes.*): memory_request, memory_limit,
    cpu_request, cpu_limit, amd_gpu_limit, nvidia_gpu_limit,
    service_account_name, default_image_name,
    default_r_environment_management, default_py_environment_management

Fields to apply via the env-var API

  • environment (non-secret name→value map) is read straight from the config
    and set on the content.

  • secrets — follow Publisher's model exactly. The config's secrets array
    holds secret names only; values are never stored in the TOML or anywhere
    on disk. Publisher keeps values in-memory in its webview for the session, does
    not persist them (only credentials/Snowflake tokens use VS Code SecretStorage),
    and re-prompts (names reload with empty values) on the next session. At publish
    it sends only the names the user actually filled in, merges them over
    environment via mergeEnvVars (secret values win on key collision), sets them
    on the content flagged as secret (encrypted/masked), and omits any
    declared name with no value (it never sends empty values).

    CLI analogue (the faithful equivalent of Publisher's per-session entry): at
    deploy/sync time, source each declared secret's value ad hoc — from the
    process environment ($NAME) and/or explicit --secret NAME=VALUE flags — never
    from a file. Merge over environment (secret wins), set flagged as secret, and
    skip any declared name with no supplied value (warn, don't fail). rsconnect
    already has an env-var code path (--environment / executor env_vars) to reuse
    for the actual set call.

Part 1 — Post-deploy hook

Goal: after a successful Connect/SPCS deploy that has an applicable
.posit/publish config, apply that config's Connect-side settings to the deployed
content.

Design sketch:

  • Add a RSConnectClient method for the content-settings PATCH
    (PATCH v1/content/{guid}) if one does not already exist, plus reuse the existing
    env-var setter.
  • Add a builder in rsconnect/publisher/ that maps a PublisherConfig to the
    content-settings payload (the rsconnect analogue of connectContentFromConfig)
    and to the merged env-var map (analogue of mergeEnvVars). Read connect.*,
    environment, secrets, description from the config (they currently live in
    PublisherConfig.extra; consider promoting to managed fields).
  • Call it from RSConnectExecutor after wait_for_task completes, gated to
    Connect/SPCS
    (same gate as _save_publisher_metadata), best-effort (warn,
    don't fail the deploy — the bundle already deployed).

Validation / edge cases (mirror Publisher's preflight):

  • Runtime settings are invalid on static content → skip/warn.
  • Validate min_processes <= max_processes; clamp/validate *_request <= *_limit;
    respect server scheduler.*_limit maxima where discoverable.
  • run_as / service_account_name require admin; run_as_current_user and
    Kubernetes require server license/config — surface a clear warning when the
    server rejects, rather than aborting.
  • secrets: follow Publisher's model (see the env-var section above) — names live
    in the TOML, values are sourced ad hoc at run time ($NAME / --secret NAME=VALUE), never persisted; unset names are skipped, not sent empty; supplied
    values are set flagged as secret and override environment on key collision.
  • No-op cleanly when the config declares none of these fields.

Part 2 — rsconnect sync command

Goal: apply the TOML config's Connect-side settings to already-deployed content
ad hoc, without rebuilding or redeploying a bundle. This is the "I edited the
config, push the settings to Connect" path.

Behavior:

  • rsconnect sync [PATH]PATH defaults to the current directory, like
    redeploy.
  • Resolves the applicable .posit/publish config (single, --config-name, or
    entrypoint match) and the target content GUID from the matching deployment
    record.
  • Server targeting: accept --server/--name/--api-key; when none are
    given, default to the server in the config / deployment record, resolving a
    saved rsconnect-python credential by normalized URL (reuse
    _find_saved_server_by_url, as redeploy does).
  • Applies exactly the Part 1 post-deploy hook logic against the resolved content —
    no bundle build, no upload, no new deployment record (though it may refresh
    the record's timestamp/settings snapshot; TBD).
  • Errors clearly if there is no deployment record (nothing to sync to) and no
    destination was supplied.

Shared code: Part 1 and Part 2 must call the same
config→(content-settings + env-vars)→API applier so behavior can't drift.

Acceptance criteria

  • A config's description, connect.runtime.*, connect.access.*, and
    connect.kubernetes.* are applied to the content on deploy/redeploy
    (Connect/SPCS only), best-effort.
  • A config's environment is applied as env vars on deploy/redeploy.
  • secrets follow Publisher's model: names only in the TOML; values sourced ad
    hoc ($NAME / --secret NAME=VALUE), never persisted; unset names skipped;
    supplied values set flagged as secret and overriding environment on
    collision.
  • rsconnect sync [PATH] applies the same settings ad hoc to already-deployed
    content, with no bundle rebuild.
  • sync targets the config/record server by default and honors
    --server/--name/--api-key overrides.
  • Server-side validation failures (limits, licensing, admin-only) warn clearly
    without corrupting the deploy.
  • Tests cover the config→payload mapping, the deploy hook, and sync
    (including default vs. explicit server targeting).
  • CHANGELOG updated.

References

  • PR: Posit Publisher .posit/publish: TOML interop, redeploy, and config-driven bundling #830
  • Publisher config→content-settings: extensions/vscode/src/bundler/connectContentFromConfig.ts
  • Publisher env-var merge: extensions/vscode/src/publish/publishShared.ts (mergeEnvVars)
  • Publisher secrets model (names in config, values in-memory per session, not persisted): extensions/vscode/webviews/homeView/src/stores/home.ts (in-memory secrets map, reloaded with empty values), extensions/vscode/src/views/homeView.ts
  • Publisher publish sequence + preflight validation: extensions/vscode/src/publish/connectPublish.ts
  • Already-implemented manifest side (integration_requests): rsconnect/bundle.py (overlay_manifest), rsconnect/publisher/store.py (config_manifest_overlay)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions