feat(console): wire list_service_accounts into New Fleet's k8s field group (studio#104) - #109
Closed
brettchien wants to merge 1 commit into
Conversation
…group (studio#104) Fifth sub-item of #104, stacked on #108. Service account (optional) becomes a <select> (was plain text) populated from list_service_accounts(context, namespace) — unlike namespace, a service account must already exist for k8s to accept it as a pod's serviceAccountName, so (unlike namespace's <input>+ <datalist>) a plain select with no free-text escape hatch is the right shape here, matching context's treatment. Reload triggers: context change (cascades into namespace + service-account reload) and namespace field's "change" event (fires on blur/commit, not per keystroke — avoids a tool call per character typed). Per #104's design this tool's failures are deliberately silent — unlike list_k8s_contexts/list_namespaces (which show a status message on failure), any error here, including an RBAC-denied list (common against a scoped-down cluster identity), just falls back to the "namespace default" option with no status shown. The field is optional and the whole point of default-SA fallback is that it's fine not to have a definitive answer here. Verified locally: npm run typecheck clean, npm test 102/102, npm run build succeeds. Ref: studio#104.
This was referenced Aug 27, 2026
brettchien
added a commit
that referenced
this pull request
Aug 27, 2026
…104) t_provision now branches on a new provider arg (default "aws", unchanged behavior): "k8s" calls provision_from_library_k8s (#114) with context/ expected_principal taken as direct args, not resolved via a fleet name — the console's identity form already collects context/namespace/service- account directly (#108/#109), and for a brand-new fleet there's no existing K8sFleetBinding to look up anyway. Fleet-scoped k8s lookup (for redeploying into an already-known k8s fleet) is left as explicit future work, not needed for this dispatch to exist. Also updates deploy_provision's tool description, which was stale after #113 (redeploy no longer requires an existing manifest) and needed the new provider/context/expected_principal params documented. NOTE — branch lineage: this stack (#112→#113→#114→this) was cut from `main` before #104's original stack (#105-110) merged, not from #110 — so K8sFleetBinding.expected_principal (originally #107) is re-added here too. Identical field in both places; trivial merge conflict to resolve whenever both land, flagging explicitly rather than silently duplicating without a note. With this, deploy_provision fully supports k8s end-to-end (provisioning side) — the remaining piece for full onboarding is unblocking console's k8s "Next" button (#108/#109's placeholder), a separate follow-up. Ref: studio#104.
Contributor
Author
brettchien
added a commit
that referenced
this pull request
Aug 28, 2026
…accounts, expected_principal, console provider picker+wiring, fleetsK8sToml.ts) (#115) * feat(studio-cp,oab-mcp): list_namespaces / list_service_accounts tools (studio#104) Second sub-item of #104 (stacked on #105's list_aws_profiles/list_k8s_contexts, same tools() vec / same design). Both are context-scoped k8s discovery: - list_namespaces(context?): Api<Namespace>::list() for the given/current kubeconfig context. Backs the New Fleet wizard's namespace <select> — a manual-entry fallback covers a brand-new namespace that doesn't exist yet. - list_service_accounts(context?, namespace): Api<ServiceAccount>:: list(namespace). Backs the optional service-account <select>. Per the design, any failure here (including an RBAC-denied list, which is common against a restricted-scope cluster identity) should read to the caller as "leave it unset" (the namespace's default service account applies), not as an error to surface — so unlike list_aws_profiles/list_k8s_contexts this one doesn't split exists/error, it just errors normally. Same k8s_client_for() helper factors out the from_kubeconfig+Client::try_from boilerplate observe_k8s_identity already had inlined. Ref: studio#104. * feat(studio-cp,oab-mcp): K8sFleetBinding.expected_principal + k8s_fleet_config_write (studio#104) Third sub-item of #104, stacked on #106. - K8sFleetBinding gets expected_principal: Option<String>, deliberately named to match FleetBinding's AWS-side field — the verify machinery already exists symmetrically (observe_k8s_identity's SelfSubjectReview- derived principal + k8s_principal_kind, same shape as observe_identity/ identity_matches for AWS), this just wires the config schema to it. Typically a system:serviceaccount:<ns>:<name> string, or a plain username; unset = no identity check for that fleet. - k8s_fleet_config_write: new MCP tool mirroring fleet_config_write's AWS-side write path (validate text parses, write bytes verbatim so comments/layout survive, return the parsed fleets + raw text). save_k8s_bindings_text (studio-cp/lib.rs) already existed and needed zero changes — it's a generic toml::from_str + verbatim write, so it picked up the new field automatically once added to the schema structs. Unlike AWS bindings, k8s bindings aren't cached anywhere in OabMcp yet (nothing dispatches provisioning to K8sDriver yet either — separate item), so this is a plain validate-then-write, no in-memory state to invalidate. Ref: studio#104. * feat(console): provider picker + k8s context/namespace fields in New Fleet wizard (studio#104) Fourth sub-item of #104, stacked on #107. Adds the console-side UI half of the k8s onboarding design — the "+ New fleet" identity form (previously AWS-only: Region/Credential profile/Principal) now starts with a Provider select (AWS / Kubernetes), toggling between the existing AWS field group and a new k8s group: - Context: <select>, populated live from the new list_k8s_contexts tool (#105), current-context flagged in the label. - Namespace: text input + <datalist> from list_namespaces (#106), scoped to whichever context is selected — deliberately NOT a plain <select>, since a brand-new namespace is a valid choice per #104's design and a select can't express "not in this list yet". - Service account (optional): plain text input for now (list_service_accounts wiring is a smaller follow-up; per #104's design any failure there should silently fall back to the namespace's default SA anyway, so a live <select> buys less than it does for context/namespace). **k8s submission is intentionally blocked, not wired through**: the identity form's submit handler shows "Kubernetes provisioning isn't available yet — tracked in #104" and refuses to advance to the Compose step when provider=k8s. This is deliberate, not a placeholder oversight — traced deploy_provision's call chain this same session and found it requires an already-stored manifest (K8sDriver::apply doesn't persist one the way ECS's apply_manifests does), so wiring k8s all the way to a live deploy_provision call would either silently fail or need the driver-dispatch design question resolved first (posted to #104, unresolved as of this commit). Shipping the picker/discovery UI now is still real progress — AWS path is 100% unchanged, and this is testable/mergeable independent of how the deploy_provision question resolves. Verified locally (no OOM constraint here — TS/vite, not cargo): `npm run typecheck` clean, `npm test` 102/102 passing, `npm run build` succeeds. Ref: studio#104. * feat(console): wire list_service_accounts into New Fleet's k8s field group (studio#104) Fifth sub-item of #104, stacked on #108. Service account (optional) becomes a <select> (was plain text) populated from list_service_accounts(context, namespace) — unlike namespace, a service account must already exist for k8s to accept it as a pod's serviceAccountName, so (unlike namespace's <input>+ <datalist>) a plain select with no free-text escape hatch is the right shape here, matching context's treatment. Reload triggers: context change (cascades into namespace + service-account reload) and namespace field's "change" event (fires on blur/commit, not per keystroke — avoids a tool call per character typed). Per #104's design this tool's failures are deliberately silent — unlike list_k8s_contexts/list_namespaces (which show a status message on failure), any error here, including an RBAC-denied list (common against a scoped-down cluster identity), just falls back to the "namespace default" option with no status shown. The field is optional and the whole point of default-SA fallback is that it's fine not to have a definitive answer here. Verified locally: npm run typecheck clean, npm test 102/102, npm run build succeeds. Ref: studio#104. * feat(console): add fleetsK8sToml.ts, the client-side write helper for fleets-k8s.toml (studio#104) Sixth sub-item of #104, stacked on #109. Mirrors fleetToml.ts for fleets-k8s.toml, same rationale: k8s_fleet_config_write (#107) has no partial/append primitive, so the client computes the new/edited TOML text and calls it with the whole file. `findFleetBlock`/`appendMember` are identical between the two files (same `[fleet.<name>]` table shape, same `members = [...]` array, neither references any AWS-specific field) — reused via import from fleetToml.ts rather than duplicated. Exported `quote()` from fleetToml.ts (was a private helper) so both modules share the one implementation. Only `appendK8sFleetBlock` (creating a brand-new fleet block) is new code — required/optional fields differ from AWS's appendFleetBlock: `context` (optional) + `namespace` (required, always written) instead of region+profile, same `expected_principal` (optional) as AWS. **Not wired into deploy.ts's submit flow yet** — the k8s identity form still can't submit (see #108: deploy_provision has no k8s dispatch, architecture question still open in #104). This PR is the write-path helper only, same "build the piece, wire it once its dependency resolves" pattern as #107's k8s_fleet_config_write tool (also not yet called by anything). Verified locally: npm run typecheck clean, npm test 106/106 (4 new tests), npm run build succeeds. Ref: studio#104.
brettchien
added a commit
that referenced
this pull request
Aug 28, 2026
…est-creation fix, k8s deploy_provision dispatch) (#116) * feat(oabctl): expose create.rs's AWS-placement defaults for reuse (studio#111) First sub-item of #111 ("+ New fleet" first-creation gap). oabctl create's interactive wizard collects VPC/subnet/security-group among other things before it can build a manifest and apply it — but the VPC/subnet/SG parts are already effectively "sensible defaults", not real interactive choices: select_subnets already auto-picks (private+NAT > private > public, up to 3 AZs) with zero prompting, and "Create new (oab-{name})" is the SG wizard's own default suggestion. Makes `list_vpcs`/`VpcInfo`, `select_subnets`/`SubnetInfo`, `list_security_groups`/`SgInfo` pub (was module-private, module itself was `mod create` — now `pub mod create`), and adds one new function, `default_security_group`, extracting the "create new (oab-{name}), reuse if it already exists" logic `run()` has inline into something callable without the interactive prompt path. No behavior change to `run()`/`oabctl create` — pure visibility change plus one new function built from existing, already-working logic (the AWS calls in `default_security_group` are the same calls `run()`'s inline SG-create branch already makes). This is groundwork only — nothing calls these yet. Next: the console-facing provision path needs a VPC choice too (unlike subnet/SG, there's no zero-prompt default for "which VPC" today — `run()` always asks). Then wiring these into a "build a default manifest, apply via deploy_apply's path" function is the actual fix for #111. Local build/test hit the known aws-sdk-ec2 OOM constraint on this machine (same pre-existing environment issue documented in prior PRs' descriptions) — change is a mechanical visibility change + one function built from already-proven inline logic, hand-verified against the diff. CI is the gate. Ref: studio#111. * feat(oabctl): add default_vpc — zero-prompt VPC pick when the account has exactly one default (studio#111) Follow-up on this same PR: VpcInfo now carries is_default (was folded into the human-readable label only, not usable programmatically). default_vpc() picks the account/region's default VPC when there's exactly one — the closest zero-prompt equivalent to what select_subnets/default_security_group already give for subnet/SG, since run()'s wizard never had a non-interactive default for VPC choice at all. Deliberately errors (not a heuristic guess) when there's zero or more than one default VPC — a caller with an explicit VPC choice (e.g. future per-fleet config, mirroring how fleets.toml already carries region/profile) should skip this and pass that VPC straight to select_subnets/default_security_group instead. Still groundwork — nothing calls this yet. Ref: studio#111. * feat(oabctl): add default_networking — SdkConfig-only entry point for VPC/subnet/SG defaults (studio#111) Follow-up on this same PR. Wraps default_vpc + select_subnets + default_security_group behind one function that takes an aws_config:: SdkConfig, not an Ec2Client — so studio-cp (which doesn't depend on aws-sdk-ec2 directly) can reach it without adding that dependency, keeping Ec2Client an oabctl-internal detail (same "RuntimeDriver is the only layer with vendor terms" boundary ADR-2 already established elsewhere). Still groundwork — nothing calls this yet. Next: build_default_manifest in studio-cp, calling this + spec defaults (resources 256/512, empty secrets, FARGATE/X86_64), then wire provision_from_library to branch create-vs- redeploy based on whether load_manifest finds a stored manifest. Ref: studio#111. * feat(studio-cp,oabctl): build a default manifest when none exists yet — the actual fix for #111 provision_from_library (backing deploy_provision / the console's "+ New fleet" flow) now checks load_manifest first: if a manifest is already stored, behavior is unchanged (redeploy patches image/bundle_from and re-applies). If none exists, it builds a fresh OABServiceManifest (build_default_manifest) using #112's zero-prompt defaults (VPC/subnet/SG via default_networking, resources 256/512, FARGATE/X86_64, empty secrets — see #111's comment thread for why empty secrets is valid and not a gap this needs to solve) and applies it via provision_manifest, a new oabctl:: studio_api helper that mirrors provision() but takes a structured manifest instead of YAML text (keeps serde_yaml an oabctl-internal detail). This is the actual fix: the console's "+ New fleet" wizard can now create a genuinely new agent end-to-end, not just redeploy an agent someone already created via the CLI. Once merged, k8s's deploy_provision dispatch (#104) lands on this same branch point — load_manifest is provider-agnostic (S3 key, not ECS-specific), so the create-vs-redeploy check doesn't need to change for k8s; only the "build a fresh manifest" + "apply it" halves need a Runtime::Kubernetes(...) branch alongside this Runtime::Ecs(...) one. configFrom for the new manifest points at artifacts/{ns}/{name}/config.toml — the same key Bundle::artifact_objects already uploads a copy of the composed config.toml to, and the same convention oabctl create's wizard uses. Unit-tested (default_config_from_uri_matches_artifact_objects_key). Manually verified every field against crates/oabctl/src/manifest.rs's struct definitions (OABServiceManifest/Metadata/Spec/Resources/Runtime/ EcsRuntime/EcsNetworking) and OABServiceManifest::validate()'s requirements (apiVersion "oab.dev/v2", kind "OABService", CPU "256" is in VALID_ECS_CPU, capacityProvider "FARGATE" is valid) — this is the riskiest change this session (real infra creation), so more care than usual went into checking it by hand given the environment's known aws-sdk-ec2 OOM constraint prevented a local cargo check. CI is the gate. Ref: studio#111. * feat(studio-cp,oabctl): k8s deploy_provision dispatch (studio#104, resumed after #111) Resumes #104's k8s deploy_provision work now that #111 (#112/#113) gave both drivers a shared, provider-agnostic create-vs-redeploy branch point — load_manifest is just an S3 key lookup, it doesn't care which Runtime variant a stored manifest holds. - oabctl::studio_api::provision_k8s: provision_manifest's k8s counterpart, applies through K8sDriver instead of EcsDriver. Takes two separate credential contexts (aws_config for the S3 bundle carrier — hooks.pre_seed is provider-agnostic, still S3 regardless of runtime — and a kubeconfig context for the actual apply) since k8s provisioning genuinely needs both simultaneously, unlike the ECS path where one SdkConfig covers everything. - studio-cp::build_default_k8s_manifest: Runtime::Kubernetes counterpart to build_default_manifest. No VPC/subnet/SG (ECS-only networking concept); service_account comes from K8sFleetBinding.expected_principal when it names one (k8s_service_account_from_principal extracts the bare name from the system:serviceaccount:<ns>:<name> form the New Fleet wizard's service- account picker writes — KubernetesRuntime.service_account wants the bare name, verified against k8s_driver.rs's own build_deployment_wires_service_account_and_node_selector test). - studio-cp::provision_from_library_k8s: provision_from_library's k8s counterpart. Compose/bundle-upload logic is duplicated rather than shared for now (deliberate — avoids reworking provision_from_library's shape again while it's still unmerged; worth revisiting once both paths are proven). Redeploy preserves the stored manifest's k8s runtime config, only bumps the image, same guarantee the AWS path gives. Nothing calls provision_from_library_k8s yet — oab-mcp's deploy_provision tool still needs a provider param to dispatch to it (the OabMcp struct also has no k8s-fleet-binding awareness yet to resolve context/expected_principal from). That wiring is the next piece. Manually verified every field against manifest.rs's struct definitions, same care as #113 given the environment can't locally compile (aws-sdk-ec2 OOM) — CI is the gate. Ref: studio#104, studio#111. * feat(oab-mcp): wire deploy_provision's k8s provider dispatch (studio#104) t_provision now branches on a new provider arg (default "aws", unchanged behavior): "k8s" calls provision_from_library_k8s (#114) with context/ expected_principal taken as direct args, not resolved via a fleet name — the console's identity form already collects context/namespace/service- account directly (#108/#109), and for a brand-new fleet there's no existing K8sFleetBinding to look up anyway. Fleet-scoped k8s lookup (for redeploying into an already-known k8s fleet) is left as explicit future work, not needed for this dispatch to exist. Also updates deploy_provision's tool description, which was stale after provider/context/expected_principal params documented. NOTE — branch lineage: this stack (#112→#113→#114→this) was cut from `main` before #104's original stack (#105-110) merged, not from #110 — so K8sFleetBinding.expected_principal (originally #107) is re-added here too. Identical field in both places; trivial merge conflict to resolve whenever both land, flagging explicitly rather than silently duplicating without a note. With this, deploy_provision fully supports k8s end-to-end (provisioning side) — the remaining piece for full onboarding is unblocking console's k8s "Next" button (#108/#109's placeholder), a separate follow-up. Ref: studio#104.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fifth sub-item of #104, stacked on #108 (→ #107 → #106 → #105, merge in order). Wires
list_service_accountsinto the k8s field group added in #108 — "Service account (optional)" goes from a plain text input to a<select>populated from the currently selected context + namespace.<select>, not<input>+<datalist>(unlike namespace): a service account must already exist for k8s to accept it as a pod'sserviceAccountName— there's no "type a brand-new one" case the way there is for namespace (which the deploy flow can bring into existence). So no free-text escape hatch here, matching howcontextis treated.change(cascades into reloading both namespace suggestions and the service-account list) and the namespace field'schangeevent — deliberatelychange(fires on blur/commit), notinput(every keystroke), to avoid a tool call per character typed.list, which is common against a scoped-down cluster identity — falls back to the "— namespace default —" option with no status message shown. This is different fromlist_k8s_contexts/list_namespaces(feat(console): provider picker + k8s context/namespace fields in New Fleet wizard (studio#104) #108), which do show a status on failure. The field is optional and the whole contract of "default SA fallback" is that not having a definitive answer here is fine.Testing
npm run typecheckclean,npm test102/102 passing,npm run buildsucceeds — same as #108, no OOM constraint on this side of the repo.Scope note
fleetsK8sToml.ts(client-side write helper forfleets-k8s.toml) and thedeploy_provision/K8sDriverdispatch design question are still open in #104.Ref #104. Stacks on #108 (→ #107 → #106 → #105) — merge in order.