refactor(hosting): shared base for the PDS container resources #143
No reviewers
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET!143
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-116-hosting-base"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #116.
Consolidates
ATProtoNet.Aspire.Hosting. This change removes duplication only. Every publicWith*method and all Tranquil support stay, and both servers produce exactly the same application model and manifest as before.Shared base for the two container resources
AtProtoPdsContainerResourceBase : ContainerResource, IResourceWithConnectionStringholds what both resources used to declare separately:HttpEndpointName,HealthCheckPath,JwtSecretParameter, the internalHostname, andConnectionStringExpression.AtProtoPdsContainerResourceandAtProtoTranquilPdsContainerResourcederive from it and keep only their server-specific members (admin password or admin account, PLC rotation key, DPoP secret, master key, database URL, development mode).private protected, so no one outside the package can derive from the base.One implementation per duplicated method pair
WithHostname(string)×2,WithHostname(IResourceBuilder<ParameterResource>)×2WithHostname<T>(…) where T : AtProtoPdsContainerResourceBase, both overloads, onAtProtoPdsHostingExtensionsWithJwtSecret×2WithJwtSecret<T>(…), same constraint and classClearDataMounts/ClearBlobMountsplus the bind/volume bodiesReplaceStorageMount<T>(builder, source, target, isBindMount)WithAtProtoPds/WithAtProtoTranquilPdsbodiesWithPdsConfiguration<T, TPds>(…). Each public method passes only its own admin-configuration writer.IResourceBuilder<T>, so they chain straight into server-specific methods such as.WithHostname(…).WithAdminAccount(…).WithDataBindMount/WithDataVolumeandWithBlobBindMount/WithBlobVolumekeep their names and signatures. They mount different directories (/pdsholds all reference-PDS state, while Tranquil's mount holds only blobs), so only their implementation is shared.WithPlcUrl,WithCrawlers,WithHandleDomains, …) are unchanged, as the issue asks.Breaking changes
WithHostname(both overloads) andWithJwtSecretare now generic methods onAtProtoPdsHostingExtensions.AtProtoTranquilPdsHostingExtensionsno longer declares them.AtProtoTranquilPdsHostingExtensions.WithHostname(pds, …)aspds.WithHostname(…).HttpEndpointName,HealthCheckPath,JwtSecretParameterandConnectionStringExpressioninto the base class is not breaking. Constants are inlined at the call site, inherited members still resolve through the derived type names, and adding a base class is an allowed change.Tests
PdsContainerResourceTestscovers the shared behaviour as theories over aPdsFlavourenum, with expected per-server values inInlineData: image and tag, port mapping and health check, connection string, argument validation, hostname (run mode, publish mode, literal, parameter),WithJwtSecret, dropping an overridden parameter from the publish model, storage mount replacement, and consumer wiring (URL, admin password, plaintext opt-in in run versus publish mode, the wait, null checks).AspireTestHostholds the shared helpers:PublishModeBuilder,GetEnvironmentAsync,SingleResource<T>, and the flavour dispatchers.GetEnvironmentAsyncused to be duplicated, andPublishModeBuilderwas written out inline four times.With*_SetsEnvironmentAnnotation/ConfiguresAdminPasswordEnvironmenttests only asserted that some environment callback existed, whichAddAtProtoPdsalways adds.FluentApi_AllowsChainingnow pins every variable the convenience methods set, includingWithProductionModeoverriding the run-modePDS_DEV_MODE=true.ImplementsIResourceWithConnectionStringtests checked something the compiler already guarantees. A theory now pins the connection-string template instead.AtProto__Pds__AuthenticationnorAdminIdentifier.WithDataVolume()andWithBlobVolume()without a name fall back to the default volume name after a bind mount.Verification
dotnet buildof the full solution in Debug,--no-incrementaland Release: 0 warnings, 0 errors.ATPROTO_ASPIRE_MANIFEST). The Aspire folder has 103 tests (98 passed, 5 skipped).dotnet run --project samples/ManagedPdsSample.AppHost --no-build -- --publisher manifestbefore and after, in Debug and Release. The manifests are byte-identical, andAspireManifestTestswithATPROTO_ASPIRE_MANIFESTandATPROTO_REQUIRE_INTEGRATION=1gives 6/6 passed.With*method, including parameter and literal hostnames, every secret override, bind mounts and volumes,WithDatabase/WithDatabaseUrl, the derived admin handle, and consumers with and without the wait.Deferred
Hosting/belongs to WP17.src/. The duplicated code is gone, but the new base class and the internal helpers carry their own XML docs, and the merged docs now cover both servers.Add*methods, left alone to keep the diff focused: thePdsContainerPort/DefaultTagconstants and the 4-line publish-mode hostname parameter.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
💬 Verdict: comment — the reviewer did not submit a structured review; the raw output follows.
(empty)
⏱ 2m 58s · 8 steps · $0.0342 · tokens: 147.7k in / 6.7k out (+289.4k cache-read) · Go: 5h 17% · wk 93% · mo 79%
📜 Full session transcript
@opencode-reviewer
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
💬 Verdict: comment — the reviewer did not submit a structured review; the raw output follows.
Let me check the recent git history to understand what changed since my last (empty) review pass:
⏱ 30m 2s · 16 steps · $0.1115 · tokens: 365.7k in / 21.5k out (+866.3k cache-read) · Go: 5h 22% · wk 95% · mo 80%
📜 Full session transcript
@claude-reviewer
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
@claude-reviewer
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
Solid, mechanical refactor. The new
AtProtoPdsContainerResourceBasecleanly hoists the shared members (HttpEndpointName,HealthCheckPath,JwtSecretParameter,Hostname,ConnectionStringExpression), and theprivate protectedconstructor correctly seals it to the assembly while stayingpublicfor API discoverability. The genericWithHostname<T>/WithJwtSecret<T>/ReplaceStorageMount<T>/WithPdsConfiguration<T,TPds>methods preserve extension-method resolution (T is inferred from the concrete resource type at call sites), and tracing through the before/after bodies forWithAtProtoPds/WithAtProtoTranquilPdsand the mount-clearing helpers shows behavior is preserved exactly (same env keys, same admin wiring, same mount-replacement semantics).The binary-compatibility reasoning in the PR description also checks out: moving
constfields is compile-time-inlined at call sites so it can't break already-compiled consumers, and moving a non-virtual instance member (ConnectionStringExpression) to a base class is resolved by the CLR through the inheritance chain, so existing callers still bind correctly.Test coverage is thorough — the new
PdsContainerResourceTeststheories exercise both flavours through the shared surface, and the per-server test files were correctly pruned to keep only server-specific assertions. Nothing incorrect found; this looks safe to merge.⏱ 1m 10s · 1 turns · tokens: 58.0k in / 6.8k out (+18.6k cache-read)
📜 Full session transcript
🤖 Resolving the merge conflicts — the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the fix runs.
@claude /review — opencode-bot is paused at its weekly Go cap (its two runs on this PR produced no structured review), so requesting a claude-bot review instead.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
This PR is unchanged in substance from the version I approved in my previous pass — same shared base class, same generic
WithHostname<T>/WithJwtSecret<T>/ReplaceStorageMount<T>/WithPdsConfiguration<T,TPds>extraction, same test consolidation intoPdsContainerResourceTestswithAspireTestHosthelpers.I re-traced the mechanics that matter most for a refactor like this:
AtProtoPdsContainerResourceBase'sprivate protectedconstructor correctly seals construction to the assembly while the class itself stayspublic abstractfor discoverability; both derived sealed classes call it with(name, jwtSecret)matching the base signature.PdsParameterOverrides.Replace<TResource>is already generic overIResource, so the new genericWithHostname<T>/WithJwtSecret<T>bind cleanly against it — no surprises there.IResourceBuilder<out T>in Aspire.Hosting is covariant (confirmed via the Aspire source/docs), so the test helpers'AddPds/WithPds/ReplaceStoragepattern-matching againstIResourceBuilder<AtProtoPdsContainerResourceBase>and switching on the concreteIResourceBuilder<AtProtoPdsContainerResource>/IResourceBuilder<AtProtoTranquilPdsContainerResource>types compiles and behaves as intended.JwtSecretParameterandConnectionStringExpressioninto a new base class is not breaking) matches documented .NET assembly-versioning behavior: member resolution against a MemberRef token walks the full inheritance chain, not just directly-declared members, so already-compiled callers resolve correctly against the refactored library.WithPdsConfiguration<T, TPds>preserves the exact env-var wiring and ordering bothWithAtProtoPdsandWithAtProtoTranquilPdshad before (URL, admin config callback, run-mode plaintext opt-in, wait), andReplaceStorageMount<T>preserves the mount-clearing-then-add semantics for both the reference PDS's/pdsand Tranquil's blob directory.No correctness issues found. Good to merge.
⏱ 1m 43s · 7 turns · tokens: 76.6k in / 10.4k out (+555.4k cache-read)
📜 Full session transcript
Merge resolved and committed (
e479625), working tree clean, no conflict markers anywhere.What conflicted:
CHANGELOG.mdonly — the only file both sides touched. Both this PR (#116) and main'sd0c4e6b(#142/#110) had inserted entries directly under[Unreleased]'s new### Breaking changes/### Changedheaders, and main additionally added### Fixedand### Removedsections; theSecuritysection below merged cleanly.How I resolved it: Kept every bullet from both sides. Within the two shared subsections I ordered main's #110 bullets above the PR's #116 bullets, following the repo's established convention of prepending the newest entry at the top of a section (verified against how #102, #99, and #93 entries were layered in past merges). Main's
FixedandRemovedsections and the PR's docs/code were untouched.Verified:
origin/mainonly the PR's 11 files (+2 changelog bullets) remain; relative to the PR branch only main's 168-file metadata cleanup remains — neither side lost anything.dotnet buildofATProtoNet.Tests(which pulls inATProtoNet.Aspire.Hosting, the project this PR refactors): 0 errors, 0 warnings after main'sDirectory.Build.propsconsolidation.[RequiresAspireManifestFact]environment gate, unchanged by the merge.→ Merge resolved and pushed; a re-review follows.
⏱ 4m 39s · 22 steps · $0.0477 · tokens: 144.6k in / 9.4k out (+648.1k cache-read) · Go: 5h 0% · wk 95% · mo 80%
📜 Full session transcript
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
@claude /review — the head moved to opencode-bot's conflict-resolution merge (
e479625, CHANGELOG only); re-review for the current head.🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
This diff is materially identical to the version I approved in my two previous passes — same
AtProtoPdsContainerResourceBaseextraction, same genericWithHostname<T>/WithJwtSecret<T>/ReplaceStorageMount<T>/WithPdsConfiguration<T,TPds>methods, samePdsContainerResourceTests/AspireTestHosttest consolidation. I re-traced the same mechanics again:private protectedbase constructor correctly restricts construction to the assembly while keeping the basepublic abstractfor discoverability; both sealed derived classes pass(name, jwtSecret)through correctly.WithPdsConfiguration<T, TPds>preserves the exact env-var wiring and ordering bothWithAtProtoPdsandWithAtProtoTranquilPdshad before (WithReference, PDS URL env, admin-config callback, run-mode plaintext opt-in, then the wait) — thewriteAdminConfigurationdelegate parameter for the reference PDS writes onlyAdminPasswordConfigurationKey, and for Tranquil writesAuthenticationConfigurationKey/AdminIdentifierConfigurationKey/AdminPasswordConfigurationKey, matching the pre-refactor bodies.ReplaceStorageMount<T>preserves the mount-clear-then-add semantics for both the reference PDS's/pdsand Tranquil's blob directory, and bothWithDataBindMount/WithDataVolume/WithBlobBindMount/WithBlobVolumecall sites pass the rightisBindMountflag.constfields (HttpEndpointName,HealthCheckPath) and the non-virtualConnectionStringExpression/JwtSecretParametermembers resolve correctly for already-compiled callers per standard CLR member-resolution-through-inheritance-chain behavior, matching the PR's binary-compatibility claim.PdsContainerResourceTeststheories overPdsFlavour) exercises the shared surface for both servers, and the per-server test files were pruned to keep only server-specific assertions without losing coverage (e.g.,WithPds_ThrowsOnNullPdsnow checks bothWithAtProtoPdsandWithAtProtoTranquilPds).No correctness issues found. Good to merge.
⏱ 0m 23s · 1 turns · tokens: 54.5k in / 2.1k out (+23.9k cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Verified the consolidation against the previous state: the shared base (
AtProtoPdsContainerResourceBase) hoists exactly the duplicated members, both sealed resources derive from it with theprivate protectedconstructor correctly limiting derivation to the package, and the extracted helpers (WithPdsConfiguration,ReplaceStorageMount, genericWithHostname/WithJwtSecret) reproduce the old bodies step for step — same environment keys and ordering, same admin wiring, same mount-replacement semantics and default volume names. Inherited consts still resolve through the derived type names used in theAdd*methods, so application-model output is unchanged. Extension-method calls keep resolving through builder covariance, and the documented binary break (recompile; rewrite explicit static calls) is legitimate and called out in the CHANGELOG. Samples contain no calls to the removed non-generic overloads. Test restructuring replaces assertion-free tests with real assertions rather than dropping coverage. No blocking issues found; the two remaining small duplications noted in the PR description are consciously deferred and not blockers. Good to merge.⏱ 2m 18s · 9 steps · $0.0296 · tokens: 109.5k in / 5.6k out (+299.0k cache-read) · Go: 5h 0% · wk 95% · mo 80%
📜 Full session transcript