Skip to content

feat(console): add fleetsK8sToml.ts, the client-side write helper for fleets-k8s.toml (studio#104) - #110

Closed
brettchien wants to merge 1 commit into
feat/k8s-onboarding-console-service-accountfrom
feat/k8s-onboarding-console-fleets-k8s-toml
Closed

feat(console): add fleetsK8sToml.ts, the client-side write helper for fleets-k8s.toml (studio#104)#110
brettchien wants to merge 1 commit into
feat/k8s-onboarding-console-service-accountfrom
feat/k8s-onboarding-console-fleets-k8s-toml

Conversation

@brettchien

Copy link
Copy Markdown
Contributor

Summary

Sixth sub-item of #104, stacked on #109 (→ #108#107#106#105, merge in order). New console/src/fleetsK8sToml.ts — the client-side text-mutation helper for fleets-k8s.toml, mirroring fleetToml.ts (used for AWS's fleets.toml). Same rationale as the original: k8s_fleet_config_write (#107) has no partial/append primitive — it overwrites the whole file — so the client computes the new/edited TOML text and calls it with the full content.

  • findFleetBlock/appendMember are reused, not duplicated — both files share the exact same [fleet.<name>] table shape and members = [...] array, and neither function references any AWS-specific field (region/profile never appear in their logic). fleetsK8sToml.ts imports and re-exports appendMember from fleetToml.ts rather than copy-pasting it.
  • Exported quote() from fleetToml.ts (was a private one-line helper) so both modules share the one implementation instead of duplicating a JSON.stringify wrapper.
  • appendK8sFleetBlock is the only genuinely new function — creates a brand-new [fleet.<name>] block. Field shape differs 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 yet

The k8s identity form still can't submit (#108 blocks it — deploy_provision has no k8s dispatch, and the architecture question posted to #104 is still unanswered). This PR is the write-path helper only, built ahead of its consumer — same pattern as #107's k8s_fleet_config_write tool, which also isn't called by anything yet. Once the deploy_provision question resolves, wiring deploy.ts's k8s submit path is: call deploy_provision (or whatever the resolved dispatch mechanism is) → on success, appendK8sFleetBlock/appendMember (mode-dependent, same branching deploy.ts already does for AWS) → k8s_fleet_config_write.

Testing

New fleetsK8sToml.test.ts, mirroring fleetToml.test.ts's coverage: appendMember works against the k8s file's shape, appendK8sFleetBlock writes all fields / omits optional ones / spaces blocks correctly. npm run typecheck clean, npm test 106/106 (4 new), npm run build succeeds.

Scope note

The deploy_provision/K8sDriver dispatch design question is still the one blocking item for full end-to-end k8s onboarding — tracked in #104, unresolved as of this PR. With this PR, every independently-buildable piece of #104's scope is now open as a PR.

Ref #104. Stacks on #109 (→ #108#107#106#105) — merge in order.

… 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 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.
@brettchien

Copy link
Copy Markdown
Contributor Author

Superseded by #115 (same content, rebuilt cleanly on current main after #105's squash-merge broke this stack's base chain — see #115 for detail). Not closing #106, already auto-closed by GitHub when its base branch was deleted.

@brettchien brettchien closed this Aug 28, 2026
@brettchien
brettchien deleted the feat/k8s-onboarding-console-fleets-k8s-toml branch August 28, 2026 02:57
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.
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