feat(compute): add an Azure Virtual Machines adapter - #161
Conversation
|
| Filename | Overview |
|---|---|
| packages/api/src/adapter-azure/AzureComputeAdapter.ts | Azure compute adapter with RG existence/case resolution, ARM CRUD, and degraded power-state enrichment. |
| packages/api/src/cloud-spi/computeSchema.ts | Azure VM schema, image/size/location constants, and coming_soon lifecycle capabilities. |
| packages/api/src/cloudProxy.ts | Registers AzureComputeAdapter in the cloud adapter registry. |
| packages/frontend/src/components/DynamicResourceView.tsx | Limits LaunchInstanceForm to AWS compute so Azure uses schema-driven create. |
| packages/api/src/adapter-azure/AzureComputeAdapter.test.ts | Hermetic coverage including case-insensitive RG accept and runtime spelling on PUT/id. |
Reviews (4): Last reviewed commit: "fix(frontend): gate the EC2 launch form ..." | Re-trigger Greptile
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
hectorvent
left a comment
There was a problem hiding this comment.
Thank you for this one, the runtime probing behind it really shows: correcting the recorded assumption that floci-az is 501 beyond blob and Cosmos, checking that Microsoft.Compute is not a catch-all stub before building on it, and the resource group existence check with the runtime's casing are exactly the kind of grounding that makes an adapter trustworthy. The hermetic tests and the honest coming_soon on start/stop/reboot (instead of advertising controls the console cannot reach) fit the capability guard's intent perfectly.
One blocker, then this is ready:
Create in the console is still the EC2 form for Azure. DynamicResourceView renders LaunchInstanceForm whenever service === "compute", with no cloud check (the inline create branch). ComputePanel gates itself on cloud !== 'aws', but the create form does not, so on Azure the Create button opens the AWS launch form: an AMI ID field, EC2 dropdowns that query the legacy /api/ec2 routes, and a submit payload of imageId/instanceType that your adapter will correctly reject with "resourceGroup is required". Could you gate that branch to service === "compute" && cloud === "aws" so Azure falls through to DynamicFormRenderer? The schema you wrote is exactly what that renderer needs, so it should light up your image and size selects for free. The README line saying create works through the generic table would then be accurate too.
Two small notes, neither blocking:
- The
cloudProxy.tsregistration block and the README service matrix row are shared with your sibling adapter PRs, so expect small rebase conflicts there depending on merge order. stopmapping topowerOffrather thandeallocateis the right call for a console stop button, and the doc comment saying so is appreciated.
Everything else checked out against the current pattern: no catalog row needed since compute and the instance type already exist, availability derives from the registration, the id encoding survives the route, and the per-VM instance view degrade keeps the list honest. Nice work.
Extends the existing `compute` category to Azure. No catalog row and no new CloudResource type — `compute` and `instance` already exist — so this adds an adapter, a schema function and one registry line, and does not touch the shared SPI surface. This corrects a recorded assumption that floci-az answers 501 for everything beyond blob storage and Cosmos. That holds for the legacy endpoints (/functions, /Tables) but not for ARM: the Microsoft.Compute provider paths are real handlers. Confirmed by checking that a bogus provider 404s with "Unsupported Microsoft.Compute path" rather than returning an empty list, so the 200s are not a catch-all stub. Verified against the runtime: create returns 201, delete 204 and the VM is gone, instanceView reports a real PowerState, and powerOff/start/ restart genuinely move a VM between running, stopped and deallocated. Decisions that follow from probing: - Resources are addressed as `resourceGroup/name`. ARM cannot address a VM without its resource group and the generic route passes one id; the id survives the route because HttpClient encodes path params. - create verifies the resource group exists first. The runtime creates a VM in a nonexistent group and returns 201, while real Azure answers ResourceGroupNotFound, so the check has to live in the adapter. - The subscription is discovered from /subscriptions rather than hardcoded, but is never presented as a scope: the runtime returns the same resources for any subscription id, so a subscription selector would be fake data for the same reason the region selector was. - start/stop/reboot are implemented and work, but are advertised as coming_soon with a reason, because there is no generic resource actions route yet — only invoke and the object routes. Advertising a control the console cannot call would break the schema-is-a-promise contract. - Power state is a per-VM instanceView fan-out, so a failure degrades the row with metadata.powerStateUnavailable instead of failing the list. Verified end to end through the route: nav entry now available for Azure, create, list, inspect by encoded id, delete, the resource-group rejection returning 400, and the schema reporting the three lifecycle verbs as coming_soon.
Azure treats resource group names as case-insensitive, so comparing them with strict equality rejected a create whose form value differed only in case from the listed group — blocking a VM creation that should succeed. The check now resolves the caller's value to the spelling the runtime uses, rather than only comparing case-insensitively. The resource id is `resourceGroup/name`, so echoing the caller's casing back would emit an id that does not match the one list() reports, and inspect or delete against it would 404. Verified through the route: creating with "RG-VM" against a runtime holding "rg-vm" returns id "rg-vm/vm-case", that id inspects 200, and a genuinely missing group still returns 400.
2c61414 to
6170111
Compare
|
Rebased onto One thing the rebase surfaced that is worth flagging: this branch never updated the README capability table. Gate green after the rebase: lint, type-check, 444 tests, build. |
DynamicResourceView rendered LaunchInstanceForm for service === 'compute' on every cloud, so Azure's Create button opened an EC2 form whose imageId/instanceType payload the Azure adapter correctly rejects. Azure now falls through to DynamicFormRenderer, which builds the form from the adapter's own schema.
|
@hectorvent Confirmed and fixed — thank you, this was a real hole and I had only exercised create through the API, never through the console. Fixed: the inline create branch in The fallback does light up the adapter's own schema for free, as you expected. Scope of what I verified: the gate, the build, and that the schema carries the right fields and select options. I have not driven the Azure create through the browser UI end to end, so if you want that confirmed before merge, say so and I will.
Also worth flagging from the rebase pass: this branch had never updated the README capability table — Gate green: lint, type-check, 444 tests, build. |
hectorvent
left a comment
There was a problem hiding this comment.
Fixed, and thank you for the comment above the condition explaining why the EC2 form is AWS only. That will stop someone simplifying it back later.
I checked it both ways, since a gate like this can break the path it was protecting. On Azure the Create button now opens your schema-driven form: VM Name, Resource Group with the hint that it must already exist, and the VM Size and Image selects, with no AMI field anywhere. On AWS the EC2 launch form is untouched, still asking for AMI, instance type and key pair. Exactly as hoped, your schema lit up the selects for free once the form fell through to the generic renderer.
I also ran the adapter end to end: a missing resource group comes back as a typed 400 rather than a 500, an invalid image lists the four valid ones, and a real create returns a normalized VM whose compound id survives the round trip.
No blockers from my side.
|
🎉 This PR is included in version 0.3.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
# [0.3.0](floci-io/floci-ui@0.2.0...0.3.0) (2026-08-18) ### Bug Fixes * **docker:** frontend dev container missing rolldown native binding ([floci-io#152](floci-io#152)) ([a531a1b](floci-io@a531a1b)) * **docker:** init scripts failing on Windows due to CRLF line endings ([floci-io#138](floci-io#138)) ([238e924](floci-io@238e924)) * **docker:** mount the Docker socket so Lambda and Cloud SQL can run ([floci-io#154](floci-io#154)) ([8ee3715](floci-io@8ee3715)) * **frontend:** colour the connection dot from the connection state ([floci-io#181](floci-io#181)) ([0e0682d](floci-io@0e0682d)) * **frontend:** show app-level validation messages in dynamic create form ([floci-io#150](floci-io#150)) ([69ba389](floci-io@69ba389)), closes [floci-io#112](floci-io#112) ### Features * **api:** derive service availability from a single catalog ([floci-io#153](floci-io#153)) ([2d62143](floci-io@2d62143)) * **aws:** add API Gateway to Cloud Explorer ([floci-io#169](floci-io#169)) ([07fe1c6](floci-io@07fe1c6)) * **aws:** add DynamoDB Cloud Explorer ([floci-io#143](floci-io#143)) ([074eef3](floci-io@074eef3)) * **azure:** add Key Vault explorer ([floci-io#147](floci-io#147)) ([90dd60e](floci-io@90dd60e)) * **compute:** add an Azure Virtual Machines adapter ([floci-io#161](floci-io#161)) ([06541a5](floci-io@06541a5)) * **foundation:** add iac service-type category ([floci-io#178](floci-io#178)) ([c9e9717](floci-io@c9e9717)) * **frontend:** collapsible sidebar with icon rail ([floci-io#180](floci-io#180)) ([2f05f46](floci-io@2f05f46)) * **gcp:** add Cloud SQL and GKE adapters ([floci-io#155](floci-io#155)) ([fd3bd2f](floci-io@fd3bd2f)) * **release:** one-button release cut from main ([floci-io#187](floci-io#187)) ([d7e940b](floci-io@d7e940b)) * **s3:** add inline preview for images, video, audio and text objects ([floci-io#177](floci-io#177)) ([8b945fd](floci-io@8b945fd)) * **serverless:** Add loading, empty, and retry states to Azure Functions view ([floci-io#136](floci-io#136)) ([c4bd78f](floci-io@c4bd78f)), closes [floci-io#114](floci-io#114) * **serverless:** improve Azure Functions metadata [floci-io#113](floci-io#113) ([floci-io#140](floci-io#140)) ([bcd21a3](floci-io@bcd21a3))
Extends the existing
computecategory to Azure.No catalog row and no new
CloudResourcetype —computeand'instance'already exist — so this is an adapter, a schema function and one registry line. It
does not touch the shared SPI surface, so it will not conflict with the other open
service PRs.
This corrects a recorded assumption about floci-az
Our notes said the Azure runtime answers 501 for everything beyond blob storage and
Cosmos. That holds for the legacy endpoints (
/functions,/Tables) but notfor ARM — the
Microsoft.Computeprovider paths are real handlers.I checked it was not a catch-all stub before building on it: a bogus provider 404s
with
Unsupported Microsoft.Compute path: notAThingrather than returning an emptylist.
Verified against the runtime:
createreturns 201,deletereturns 204 and the VMis actually gone,
instanceViewreports a realPowerState, andpowerOff/start/restartgenuinely move a VM betweenrunning,stoppedanddeallocated.Decisions that follow from probing
resourceGroup/name. ARM cannot address a VMwithout its resource group and the generic route passes a single id. The id
survives the route because
HttpClient.ts:257encodes path params.createverifies the resource group exists first. The runtime creates a VM ina nonexistent resource group and returns 201, while real Azure answers
ResourceGroupNotFound— so the check has to live in the adapter or the consolewould create resources that could never exist against a real provider.
/subscriptionsis read onceto keep emitted resource ids honest, but the runtime returns the same resources
for any subscription id — including
totally-made-up. A subscription selectorwould therefore be fake data for exactly the reason the region selector was
deferred, so none is offered.
start/stop/rebootare implemented and work, but advertised ascoming_soonwith a reason. There is no generic resource actions route yet —only
invokeand the object routes — so the console cannot call them. Advertisinga control that cannot be reached would break the schema-is-a-promise contract that
cloudProxy.test.tsexists to protect. They light up for free once the genericactions route lands.
instanceViewfan-out, so a failure degrades the rowwith
metadata.powerStateUnavailablerather than failing the whole list.than asking for all four
imageReferencefields; the map is shared with theschema so form and request cannot drift.
Verification
lint,type-check,testandbuildpass from the repo root. 16 adapter tests;hermetic — the compute, capability-guard and catalog suites (100 tests) pass with
globalThis.fetchreplaced by a throw.End to end through the route: the Azure nav entry is now
available, plus create,list, inspect by encoded id, delete, the resource-group rejection returning 400, and
the schema reporting the three lifecycle verbs as
coming_soon.Also worth knowing
Resource group
DELETEreturns 200 and does nothing on this runtime, so resourcegroups are deliberately not managed here — they have to pre-exist.