Skip to content

feat(templates): one-click demo deploy for activation (no Git account)#157

Merged
dviejokfs merged 12 commits into
mainfrom
feat/demo-template-one-click-activation
Jun 25, 2026
Merged

feat(templates): one-click demo deploy for activation (no Git account)#157
dviejokfs merged 12 commits into
mainfrom
feat/demo-template-one-click-activation

Conversation

@dviejokfs

Copy link
Copy Markdown
Contributor

What

A one-click deploy path so a brand-new user can deploy a fully-instrumented demo app — analytics, error tracking, distributed tracing, and a Postgres database — without first connecting a Git provider or building a project of their own. This is the activation unblock: see Temps in action in one click.

Pairs with the new observability-starter example in gotempsh/temps-examples (already on main).

Changes

Backend (temps-projects, temps-core)

  • POST /projects/from-template now accepts an optional git_provider_connection_id.
    • With a connection → unchanged fork-into-your-Git-account flow (now validates repository_name is present).
    • Without → deploys directly from the template's public source repo (is_public_repo + git_url + the template subfolder as the build directory). Owner/repo labels are derived from the public URL so the deploy pipeline plans the clone and auto-queues the first deploy.
  • New TelemetryEventKind::ProjectCreatedFromTemplate (non-identifying template_slug / forked / service_count), emitted alongside ProjectCreated.

Frontend (web)

  • TemplateConfigurator no longer dead-ends at "No Git Provider Connected" — with no connection it shows a "deploy from public source" affordance and deploys in public-repo mode; fork-only fields (repo name/owner/visibility) are hidden.
  • Auto-selects an existing service that satisfies a template requirement (e.g. an existing Postgres) so returning users get it attached with zero extra clicks; new users create one via the existing Add Service flow.
  • FirstProjectOnboarding empty state gains a "Try the demo app" banner that one-click deploys the template (only shown when there are zero projects, by construction).

Template

  • observability-starter registered in temps-core/templates.yaml (featured, postgres service, → gotempsh/temps-examples subfolder examples/observability-starter).

Tests / verification

  • cargo test -p temps-core -p temps-projects → 179 + 46 pass (new tests for bundled templates + public-URL owner/repo parser; telemetry count updated). Clippy clean.
  • Frontend tsc + eslint clean.
  • Live (local instance): one-click deploy (no Git connection) → 201, project created with correct subfolder / public-repo / derived-owner fields, environment + deployment auto-queued. Fork-mode validation returns 400 without a repo name.

Notes

  • Public-repo deploys can't receive push webhooks, so auto-deploy-on-push is disabled in that mode (re-deploy is manual / via redeploy).
  • SDK change is hand-scoped to the two now-optional fields on CreateProjectFromTemplateRequest to avoid unrelated regen drift on this branch.

Rollback previously reused the target deployment's stored Docker image. That
path is fragile:
- The nightly cleanup prunes dangling images after ~7 days, so rolling back to
  anything older fails with "image no longer exists locally".
- It sets health_check_path=None, routing traffic before the container is
  confirmed listening.
- It can't reconstruct static deployments (the prior image lacks a server
  runtime).

For git-sourced projects that carry a commit/branch, rollback now re-runs the
full build pipeline at the target deployment's commit via trigger_pipeline,
instead of reusing the image. This always works (no dependency on a surviving
image), goes through the same build + health-check pipeline as a normal deploy,
and rebuilds static bundles correctly. Non-git projects (docker_image /
static_files / manual without a git ref) have no source to rebuild and keep the
existing image-reuse path unchanged.

Mechanism: GitPushEventJob gains an optional rollback_from_deployment_id
(#[serde(default)] for back-compat). When set, process_git_push_event marks the
created deployment is_rollback / rolled_back_from_id and tags context_vars with
trigger=rollback. trigger_pipeline delegates to a new trigger_pipeline_inner
that carries the marker; the public signature is unchanged.

Tests: new test_rollback_rebuilds_from_source_for_git_projects asserts a
git deployment with a commit takes the rebuild path (no synchronous image-reuse
row). Existing test_rollback_to_deployment (no git ref) still exercises and
passes the image-reuse path.
… only when gone

The git-rollback path rebuilt from source unconditionally, even when the
target deployment's image was still in the local Docker cache. Reusing a
present image is near-instant and byte-identical to what we're rolling back
to, so gate the rebuild on image availability: reuse when the image exists,
fall back to rebuild-from-source only when it's pruned or the preset is
static (no runnable image). Non-git projects are unchanged.

Reworks the rollback tests to cover both branches (image present -> reuse,
image missing -> rebuild) and corrects the CHANGELOG wording.
Add an "observability-starter" template and a one-click deploy path so a
brand-new user can deploy a fully-instrumented demo app (analytics, error
tracking, tracing, Postgres) without first connecting a Git provider or
building a project of their own.

Backend (temps-projects):
- POST /projects/from-template now accepts an optional
  git_provider_connection_id. When omitted, the project deploys directly
  from the template's PUBLIC source repo (is_public_repo + git_url + the
  template subfolder as the build directory) instead of forking it. Owner/repo
  labels are derived from the public URL so the deploy pipeline plans the clone
  and auto-queues the first deploy. Fork mode is unchanged and now validates
  that repository_name is present.
- Emit ProjectCreated + new ProjectCreatedFromTemplate telemetry events
  (with non-identifying template_slug / forked / service_count) on success.

Frontend (web):
- TemplateConfigurator no longer dead-ends at "No Git Provider Connected".
  With no connection it shows a "deploy from public source" affordance and
  deploys in public-repo mode; fork-only fields (repo name/owner/visibility)
  are hidden. git_provider_connection_id is now optional in the form/SDK.
- Auto-select an existing service that satisfies a template requirement
  (e.g. an existing Postgres) so returning users get it attached with zero
  extra clicks; new users create one via the existing Add Service flow.
- FirstProjectOnboarding empty state gains a "Try the demo app" banner that
  one-click deploys the observability-starter template (only shown when there
  are zero projects, by construction).

Template registered in temps-core/templates.yaml pointing at
gotempsh/temps-examples (subfolder examples/observability-starter), featured,
postgres service. Tests added for the bundled templates and the public-URL
owner/repo parser; telemetry count assertion updated.
@dviejokfs dviejokfs changed the base branch from feat/rollback-from-source to main June 25, 2026 08:47
@dviejokfs

Copy link
Copy Markdown
Contributor Author

Note on stacking: This PR is based on feat/rollback-from-source (#155) and now targets main, so its diff currently includes #155's 4 rollback commits. Merge #155 first — once it lands in main, this PR's diff cleans up to just the single feat(templates): one-click demo deploy commit. The only commit owned by this PR is 110b07a9.

Adds an image-based deploy path for templates so the one-click "Try the demo
app" activation flow can pull and run a prebuilt image in seconds instead of
building from source (which is multi-minute and BuildKit-heavy). Build-from-
source remains the fallback for templates without an image.

- temps-core: ProjectTemplate gains optional `image` / `exposed_port` /
  `health_check_path`; new `Job::DeployImageRequested` (project-scoped, mirrors
  GitPushEvent). templates.yaml: observability-starter now carries
  `image: ghcr.io/gotempsh/observability-starter:latest` + port 3000, keeping
  its `git:` block as the build fallback.
- temps-projects: create_project_from_template gains an image branch — when the
  template has an `image`, it creates a docker_image project (exposed_port from
  the template) and fires Job::DeployImageRequested; otherwise the existing
  fork / public-repo build paths run. ProjectCreatedFromTemplate telemetry now
  carries `deploy_mode` (image|fork|public_repo). TemplateResponse exposes the
  three new fields.
- temps-deployments: job_processor consumes Job::DeployImageRequested — resolves
  the project's production environment(s) and creates a source_type=docker_image
  deployment (external_image_ref in metadata) so the workflow planner skips
  download_repo+build_image and plans a pull+run pipeline. Mirrors the
  deploy_from_image handler.
- web: TemplateConfigurator shows a "deploys instantly from a prebuilt image"
  note and hides the Git/source section for image templates; SDK TemplateResponse
  type extended (hand-scoped to avoid regen drift).

Image-mode wins over a Git connection (fastest activation). The prebuilt image
is published to GHCR by CI in gotempsh/temps-examples.
@dviejokfs

Copy link
Copy Markdown
Contributor Author

Added image-based template deploy (commit ffe3ca23).

What: templates can now carry a prebuilt image; the one-click flow then deploys via source_type: docker_image (pull + run, no build — instant, no BuildKit) instead of building from source. Build-from-source stays the fallback for templates without an image; image mode wins over a Git connection.

  • New Job::DeployImageRequested (project-scoped, mirrors GitPushEvent); temps-deployments job_processor consumes it → creates a docker_image deployment so the planner skips download_repo+build_image.
  • ProjectCreatedFromTemplate telemetry now carries deploy_mode (image|fork|public_repo).
  • observability-starter template points at ghcr.io/gotempsh/observability-starter:latest (port 3000), built + published multi-arch (amd64/arm64) by CI in gotempsh/temps-examples — image is live and public (verified anonymous pull).

Verification: 3 crates compile, 179 + 387 tests pass, clippy + frontend tsc/lint clean. CI image publish verified green. The live Temps-orchestrated image deploy will be exercised in a separate env (local Docker registry pulls are blocked by a Docker Desktop proxy here).

The DeployImageRequested consumer created the deployment + jobs and set the
deployment Running, but never triggered the workflow executor, so the
pull_external_image / deploy_container jobs sat pending forever. Call
workflow_executor.execute_deployment_workflow after planning (mirrors the
git-push path), and mark the deployment Failed if execution errors.

Verified end-to-end: one-click image-template deploy now pulls
ghcr.io/gotempsh/observability-starter and runs the container in ~8s (no
build), app serves 200 with all pillars.
The Redeploy / New Deployment action routed every project through the git
pipeline (trigger-pipeline), which rejects docker_image projects (no repo
info) and rendered "Deploy from: N/A, Type: Commit". Image-template projects
were therefore impossible to redeploy from the UI.

RedeploymentModal now shows an image-deploy view (image ref + environment, no
branch/commit/tag) when project.source_type === 'docker_image'. ProjectDeployments
and DeploymentDetails resolve the prebuilt image ref from the (most recent /
selected) deployment's metadata.externalImageRef and route confirmation through
deployFromImage instead of triggerProjectPipeline. Backend already supported
this (POST /projects/{id}/environments/{env}/deploy/image) — verified a
redeploy completes in ~10s re-pulling the image.
The DSN builder stripped the port (`.replace(":8080", "")`) and hard-coded
`https://`, so a local instance configured at http://host.docker.internal:8080
produced `https://<key>@host.docker.internal/4` — wrong scheme AND no port, so
the Sentry SDK sent events to :443 and they never reached the ingest. (The OTEL
endpoint was fine because it uses the full base_url.)

Replace all three DSN-build sites (create / existing / rotate) with a shared
build_dsn() that keeps {scheme}://{key}@{host[:port]}/{project_id} intact and
drops only a trailing path/slash. Verified: a redeploy now injects
SENTRY_DSN=http://<key>@host.docker.internal:8080/4. Unit test added.
The earlier redeploy fix only wired imageRef into the empty-state modal
instance; the primary RedeploymentModal (rendered when deployments exist) still
lacked it, so its image showed 'N/A' and the Deploy button stayed disabled —
blocking redeploy from the UI. Wire imageRef + redeployImage.isPending into the
second instance as well. Verified in-browser: image now shows
ghcr.io/gotempsh/observability-starter:latest and Deploy is enabled.
Resolves jobs.rs conflict: GitPushEventJob gains rollback_from_deployment_id
(from #155) alongside the new DeployImageRequestedJob (this branch). Both
features coexist; workspace compiles and temps-core tests pass (179).
Also applies cargo fmt to a pre-existing long assert_eq! in dsn_service.rs.
@dviejokfs dviejokfs merged commit ade3c22 into main Jun 25, 2026
6 checks passed
@dviejokfs dviejokfs deleted the feat/demo-template-one-click-activation branch June 25, 2026 15:57
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.

1 participant