Skip to content

feat(compute): add an Azure Virtual Machines adapter - #161

Merged
hectorvent merged 4 commits into
floci-io:mainfrom
TheSaifZaman:feat/azure-compute
Aug 18, 2026
Merged

feat(compute): add an Azure Virtual Machines adapter#161
hectorvent merged 4 commits into
floci-io:mainfrom
TheSaifZaman:feat/azure-compute

Conversation

@TheSaifZaman

Copy link
Copy Markdown
Contributor

Extends the existing compute category to Azure.

No catalog row and no new CloudResource typecompute and '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 not
for ARM — the Microsoft.Compute provider 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: notAThing rather than returning an empty
list.

Verified against the runtime: create returns 201, delete returns 204 and the VM
is actually 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 a single id. The id
    survives the route because HttpClient.ts:257 encodes path params.
  • create verifies the resource group exists first. The runtime creates a VM in
    a nonexistent resource group and returns 201, while real Azure answers
    ResourceGroupNotFound — so the check has to live in the adapter or the console
    would create resources that could never exist against a real provider.
  • The subscription is discovered, never presented. /subscriptions is read once
    to keep emitted resource ids honest, but the runtime returns the same resources
    for any subscription id — including totally-made-up. A subscription selector
    would therefore be fake data for exactly the reason the region selector was
    deferred, so none is offered.
  • start/stop/reboot are implemented and work, but advertised as
    coming_soon
    with a reason. There is no generic resource actions route yet —
    only invoke and the object routes — so the console cannot call them. Advertising
    a control that cannot be reached would break the schema-is-a-promise contract that
    cloudProxy.test.ts exists to protect. They light up for free once the generic
    actions route lands.
  • Power state is a per-VM instanceView fan-out, so a failure degrades the row
    with metadata.powerStateUnavailable rather than failing the whole list.
  • The create form picks an image from a short list of known-good references rather
    than asking for all four imageReference fields; the map is shared with the
    schema so form and request cannot drift.

Verification

lint, type-check, test and build pass from the repo root. 16 adapter tests;
hermetic — the compute, capability-guard and catalog suites (100 tests) pass with
globalThis.fetch replaced 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 DELETE returns 200 and does nothing on this runtime, so resource
groups are deliberately not managed here — they have to pre-exist.

Comment thread packages/api/src/adapter-azure/AzureComputeAdapter.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an Azure Virtual Machines compute adapter on the existing compute/instance surface.

  • Registers AzureComputeAdapter and documents Azure compute as available in the README matrix.
  • Implements list/get/create/delete plus lifecycle helpers; schema marks start/stop/reboot as coming_soon.
  • Resolves resource groups case-insensitively and emits the runtime’s casing in paths and ids.
  • Routes non-AWS compute create UI through DynamicFormRenderer instead of the EC2 launch form.

Confidence Score: 5/5

This PR appears safe to merge; the prior resource-group casing issue is fixed and no blocking failures remain.

resolveResourceGroup matches resource groups case-insensitively and returns the runtime spelling for paths and ids, with tests covering acceptance and casing; no remaining blocking failure was identified on the follow-up path.

Important Files Changed

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

@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@hectorvent hectorvent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts registration block and the README service matrix row are shared with your sibling adapter PRs, so expect small rebase conflicts there depending on merge order.
  • stop mapping to powerOff rather than deallocate is 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.
@TheSaifZaman

Copy link
Copy Markdown
Contributor Author

Rebased onto fd3bd2f after #147, #152 and #155 merged. This one rebased with no textual conflicts — its CONFLICTING state was GitHub's merge computation against the moved base rather than a real clash.

One thing the rebase surfaced that is worth flagging: this branch never updated the README capability table. service-matrix.ts generates | Compute | Compute | … | Yes (list, inspect, create, delete) | No | once the Azure VM adapter is registered, but the committed table still said No for Azure. Governing rule 3 makes README drift part of the definition of done for any change to visible navigation, so I regenerated it in a separate commit (6170111). Nothing else changed.

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.
@TheSaifZaman

TheSaifZaman commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@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 DynamicResourceView is now gated to service === "compute" && cloud === "aws", so every other cloud falls through to DynamicFormRenderer as you suggested. I left a comment at the branch explaining why it is AWS-only — that it is an EC2 form querying the legacy /api/ec2 routes and submitting imageId/instanceType — so the next person does not helpfully re-generalise it.

The fallback does light up the adapter's own schema for free, as you expected. GET /api/clouds/azure/services/compute/schema returns:

name (text, required) · resourceGroup (text, required) · vmSize (select, required)
image (select, required) · location (select) · adminUsername (text)

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.

ComputePanel already self-gates on cloud !== 'aws', so nothing was needed there.

Also worth flagging from the rebase pass: this branch had never updated the README capability table — service-matrix.ts generates Yes for Azure Compute once the adapter is registered, but the committed table still said No. Regenerated in 6170111.

Gate green: lint, type-check, 444 tests, build.

@hectorvent hectorvent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@hectorvent
hectorvent merged commit 06541a5 into floci-io:main Aug 18, 2026
6 checks passed
@hectorvent

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.3.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

thomhurst pushed a commit to thomhurst/floci-ui that referenced this pull request Aug 18, 2026
# [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))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants