Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
163 commits
Select commit Hold shift + click to select a range
060118d
feat: data disk snapshot on stop for cost reduction (only Scaleway fo…
PierreBeucher Jan 22, 2026
ed3d3d7
refactor: use instance state rather than manager option to define del…
PierreBeucher Jan 22, 2026
c455d50
chore: Linode use input for instance server state
PierreBeucher Jan 22, 2026
7b9cbc2
feat: create base image on creation to remove root disk on stop for c…
PierreBeucher Jan 23, 2026
21e6bfd
chore: add tags to match resourcces to instance
PierreBeucher Jan 23, 2026
b4426c9
chore: ensure Linode instance tags won't conflict with delete before …
PierreBeucher Jan 23, 2026
2a53836
chore: ensure update sets instance in started state for configuration
PierreBeucher Jan 23, 2026
e77fa79
feat: flag to keep base image on destroy
PierreBeucher Jan 23, 2026
e3417c6
chore: update Linode integ test with new features
PierreBeucher Jan 23, 2026
943ea4d
refactor: cleanup comments, renamed components and re-ordered code
PierreBeucher Jan 23, 2026
0e67788
chore: optional Linode deleteInstanceServerOnStop (default to true)
PierreBeucher Jan 23, 2026
dcc4c06
chore: ensure linode Labels are not including unallowed characters
PierreBeucher Jan 23, 2026
2746da3
refactor: configurator skipped warn with proper logger
PierreBeucher Jan 23, 2026
899b87b
dev: allow override of Core version on container image build
PierreBeucher Jan 25, 2026
4f232a2
chore: data snapshot and base image pulumi stack output may be undefi…
PierreBeucher Jan 25, 2026
cad0073
chore: ensure Linode tags are treated like labels
PierreBeucher Jan 25, 2026
762e5a0
chore: remove MacOS 13 CI test (retired runners)
PierreBeucher Jan 25, 2026
27b9ff1
chore: force Linode deleteInstanceServerOnStop to true if undefined
PierreBeucher Jan 25, 2026
71600c6
refactor: --base-image-* flag name consistency
PierreBeucher Jan 26, 2026
d192cd1
chore: comments review
PierreBeucher Jan 26, 2026
0adf6e9
chore: test lowercase transformation
PierreBeucher Jan 26, 2026
8b19c6d
Merge pull request #338 from PierreBeucher/scaleway-data-disk-snapshot
PierreBeucher Jan 26, 2026
37a8bea
fix: some Linode labels still exceed 50 characters
PierreBeucher Jan 26, 2026
0b94cdb
feat: temporary workaround for Ansible galaxy: use Git to download co…
PierreBeucher Jan 26, 2026
c25c1c7
chore: add comments for Ansible workaround
PierreBeucher Jan 26, 2026
4723c6d
Merge pull request #339 from PierreBeucher/fix-linode-tag-length
PierreBeucher Jan 26, 2026
d756ce0
dev: add Novops to NIx dev shell
PierreBeucher Jan 27, 2026
c482394
dev: Novops config for release
PierreBeucher Jan 27, 2026
be3da1a
chore: prepare release 0.42.0 - update version in package files and s…
PierreBeucher Jan 27, 2026
63ad7e7
chore(release-0.42.0): release 0.42.0
PierreBeucher Jan 27, 2026
667867a
Merge pull request #340 from PierreBeucher/release-please--branches--…
PierreBeucher Jan 27, 2026
ee34fc3
Merge pull request #341 from PierreBeucher/release-0.42.0
PierreBeucher Jan 27, 2026
da044d6
feat: data disk and base image management for AWS, Azure and GCP
PierreBeucher Feb 5, 2026
0ecd307
dev: update test to manage instance deletion and data / OS disk snaps…
PierreBeucher Feb 15, 2026
4ea0266
ai: update rules and add hsotry of recent tasks
PierreBeucher Feb 15, 2026
29a3241
refactor: ensure state retrocompat by not adding required fields and…
PierreBeucher Feb 15, 2026
b354468
dev: add env var NODE_OPTIONS --max-old-space-size as compilation req…
PierreBeucher Feb 15, 2026
5a396f1
dev: task test-unit with arguments
PierreBeucher Feb 15, 2026
bcc483a
dev: improved tests to check resources are matching state outputs
PierreBeucher Feb 15, 2026
2734e3d
chore: inform about potentially long stopping time
PierreBeucher Feb 16, 2026
a488bc7
refactor: always use !== absent for data disk and instance status to…
PierreBeucher Feb 16, 2026
8a52d53
refactor: move provisionInput imageId to generic Core State
PierreBeucher Feb 17, 2026
bea40c1
refactor: better state override on provision, don't use empty string …
PierreBeucher Feb 17, 2026
21f7c72
chore: update with resurce checks and pass Linode test
PierreBeucher Feb 17, 2026
dd5916e
chore: manager wrong check for data snapshot provision
PierreBeucher Feb 17, 2026
8d957fb
dev: implement data snapshot, base image snapshot and instance server…
PierreBeucher Feb 17, 2026
321eb2d
chore: Linode client update
PierreBeucher Feb 17, 2026
11ca8d4
chore: compile typo
PierreBeucher Feb 17, 2026
16af324
ai: write test and run commands for feedback loop
PierreBeucher Feb 17, 2026
04c3b4e
chore: dummy provider test for both snapshot and no-snapshot lifecycle
PierreBeucher Feb 17, 2026
e3d087b
doc: remove Scaleway 3070 instances (removed from public offering)
PierreBeucher Feb 17, 2026
180984e
doc: add featutes page
PierreBeucher Feb 17, 2026
eabe140
doc: Paperspace deprecation note
PierreBeucher Feb 17, 2026
e95d910
feat: enable data disk snapshot and base image by default on creation…
PierreBeucher Feb 18, 2026
c4be850
tech: parse CLI args with Zod to avoid runtime type issues
PierreBeucher Feb 18, 2026
5057c4a
doc: broken link
PierreBeucher Feb 18, 2026
3623d41
Merge pull request #343 from PierreBeucher/disk-snapshot-all-providers
PierreBeucher Feb 18, 2026
65a0ef8
feat: bump all dependencies
PierreBeucher Feb 18, 2026
15bf2d0
chore: fix mdbook syntax and reinforce reproducibility of check
PierreBeucher Feb 18, 2026
d56fb22
Merge pull request #344 from PierreBeucher/bump-deps
PierreBeucher Feb 19, 2026
dab3078
feat: pass AZURE_CORE_USE_MSAL_HTTP_CACHE env var to internal container
PierreBeucher Feb 19, 2026
43e733f
feat: AWS zone (alongside region) selection
PierreBeucher Feb 19, 2026
704f906
feat: remove CLI flag --public-ip-type
PierreBeucher Feb 19, 2026
074f690
chore: AWS zone prompt simpler message
PierreBeucher Feb 19, 2026
d97b621
Merge pull request #345 from PierreBeucher/minor-improvements
PierreBeucher Feb 19, 2026
46bc245
feat: use a fixed NVIDIA Container Toolkit version
PierreBeucher Feb 26, 2026
86c27d6
fix: force NVIDIA Container Toolkit version to 1.17.8-1 to avoid blac…
PierreBeucher Feb 26, 2026
c95d98e
chore: prepare release 0.43.0 - update version in package files and s…
PierreBeucher Feb 27, 2026
ca47e51
chore(release-0.43.0): release 0.43.0
PierreBeucher Feb 27, 2026
25b2421
Merge pull request #349 from PierreBeucher/release-please--branches--…
PierreBeucher Feb 27, 2026
ae98341
Merge pull request #350 from PierreBeucher/release-0.43.0
PierreBeucher Feb 27, 2026
8264e63
chore: do not delete AWS static IP address on stop
PierreBeucher Mar 2, 2026
2f60eea
Merge pull request #351 from PierreBeucher/fix-aws-static-ip-deletion…
PierreBeucher Mar 2, 2026
f72bc3c
feat: add deploy/configure --ansible-args-override override
PierreBeucher Feb 26, 2026
9a10c78
fix: on start with deletedInstanceServerOnStop, if instance server wa…
PierreBeucher Feb 26, 2026
1a7d3cb
dev: fail container image build if a dependency is not available
PierreBeucher Feb 27, 2026
84355e8
fix: Lutris desktop entry name typo
PierreBeucher Feb 27, 2026
aa4c498
doc: added more debugging instructions
PierreBeucher Mar 2, 2026
7e18881
chore: partial rollback of dependency update to keep Sunshine release…
PierreBeucher Mar 2, 2026
7bbc6bc
Merge pull request #352 from PierreBeucher/minor-tweaks
PierreBeucher Mar 2, 2026
ac05cfe
feat: support data disk resize
PierreBeucher Feb 24, 2026
b4ec444
dev: add cloudypad-debug alias for easier debugging
PierreBeucher Mar 2, 2026
67292ab
feat: add --ansible-args-override option to create and start command
PierreBeucher Mar 3, 2026
f8b3e12
chore: run efsck on disk before mount if it's been unmounted
PierreBeucher Mar 3, 2026
5dafa5d
ai: more feedback loop instructions to manage infra with CLI
PierreBeucher Mar 3, 2026
da1500c
ai: history for disk resize work
PierreBeucher Mar 3, 2026
f290ed9
chore: update test to check data disk updates
PierreBeucher Mar 6, 2026
4f85b2a
dev: load LINODE_TOKEN wiht Novops
PierreBeucher Mar 6, 2026
2cfdc50
test: added Ansible playbook for testing, run during integ test
PierreBeucher Mar 6, 2026
fbb1047
feat: add Linode custom labels
PierreBeucher Mar 10, 2026
850281f
dev: shorter integ test labels
PierreBeucher Mar 10, 2026
61aec79
dev: update provider integ tests
PierreBeucher Mar 10, 2026
600f516
refactor: renamed cloudypad-check role to verify
PierreBeucher Mar 10, 2026
ba0e64e
refactor: use Ansible module to find service fact on verify
PierreBeucher Mar 11, 2026
8fb4831
dev: working rate limit check role
PierreBeucher Mar 11, 2026
517d78e
Merge pull request #353 from PierreBeucher/resize-disk
PierreBeucher Mar 11, 2026
87e386f
chore: prepare release 0.44.0 - update version in package files and s…
PierreBeucher Mar 11, 2026
cb850e1
chore(release-0.44.0): release 0.44.0
PierreBeucher Mar 11, 2026
5b67191
Merge pull request #354 from PierreBeucher/release-please--branches--…
PierreBeucher Mar 11, 2026
0e19a33
Merge pull request #355 from PierreBeucher/release-0.44.0
PierreBeucher Mar 11, 2026
dc7ff0f
ci: use self-hosted runners
PierreBeucher Mar 16, 2026
01d8b5d
dev: switch to pnpm for cache efficiency
PierreBeucher Mar 16, 2026
2ea9bbd
build: pnpm requires generous amount of memory to build
PierreBeucher Mar 16, 2026
78faa6c
ci: cleanup integ test whatever test result
PierreBeucher Mar 16, 2026
8448824
ci: don't logout docker to prevent runner machine globally being logg…
PierreBeucher Mar 16, 2026
35c0247
Merge pull request #357 from PierreBeucher/self-hosted-runners
PierreBeucher Mar 16, 2026
71e9bc3
chore: typo in comment
PierreBeucher Mar 16, 2026
c2831d4
Merge pull request #358 from PierreBeucher/typo-comment
PierreBeucher Mar 16, 2026
c1cc2b4
fix: move PNPM_HOME to /usr/local/share/pnpm so non-root users can ex…
alexjurkiewicz Apr 3, 2026
f3b61b3
feat: log message before base image snapshot creation
alexjurkiewicz Apr 3, 2026
f8d4621
fix: show actual instance name in destroy hint on create failure
alexjurkiewicz Apr 3, 2026
9be60f5
fix: Linode disk not being mounted on machine as per missing var in A…
PierreBeucher Apr 4, 2026
3972e42
chore: re-inforce data disk and other outputs testing
PierreBeucher Apr 4, 2026
a54de4e
refactor: disttinguish Linode dataDiskId and dataDiskHostPath in Pulu…
PierreBeucher Apr 4, 2026
2ba595b
Merge pull request #368 from PierreBeucher/fix-linode-data-disk-ansib…
PierreBeucher Apr 4, 2026
b9b5367
feat(aws): add --create-vpc option to provision a dedicated VPC
alexjurkiewicz Apr 3, 2026
9fe52e5
feat(aws): enable IPv6 for created VPC
alexjurkiewicz Apr 3, 2026
41741bf
refactor(aws): rename createVpc to dedicatedVpc with object type, ext…
alexjurkiewicz Apr 4, 2026
19d3e4e
Merge pull request #364 from alexjurkiewicz/feat/base-image-snapshot-…
PierreBeucher Apr 11, 2026
5d48265
Merge pull request #367 from alexjurkiewicz/fix/destroy-hint-instance…
PierreBeucher Apr 11, 2026
c39428e
Merge pull request #360 from alexjurkiewicz/feat/aws-create-vpc
PierreBeucher Apr 12, 2026
1f1050b
feat: bump to Proton 10 by default
PierreBeucher Mar 19, 2026
77124d9
fix: missing Lutris icon
PierreBeucher Mar 19, 2026
0c09a3f
feat(sunshine): add screenshotter
PierreBeucher Mar 19, 2026
825d830
chore: fix test after rebase
PierreBeucher Apr 19, 2026
b5c1a95
Merge pull request #370 from PierreBeucher/minor-updates
PierreBeucher Apr 19, 2026
6bb98cc
feat: auto-generate Sunshine Web UI password if none provided
alexjurkiewicz Apr 3, 2026
66c1c76
fix: stop logging generated sunshine password
alexjurkiewicz Apr 13, 2026
6fb6293
refactor: stronger tested password generation
PierreBeucher Apr 19, 2026
769ddb4
feat: Scaleway DNS record management
PierreBeucher Apr 12, 2026
c54511f
ai: integ test feedback loop
PierreBeucher Apr 12, 2026
9646441
refactor: Scaleway public IP allocation only when no DNS configured O…
PierreBeucher Apr 26, 2026
6042b0b
dev: Ansible playbook skip verify when CLOUDYPAD_SKIP_CONFIGURATION=true
PierreBeucher Apr 26, 2026
24b3760
ai: gh-cli skill
PierreBeucher Apr 26, 2026
25757ad
feat: bump NVIDIA driver and container toolkit version
PierreBeucher Apr 26, 2026
8b639d1
fix: use hostname rather than public IP for pairing to ensure Moonlig…
PierreBeucher Apr 26, 2026
3956753
Merge pull request #369 from PierreBeucher/scaleway-dns-record
PierreBeucher Apr 26, 2026
06f3bca
dev: uncomment Linode test, use bigger instance
PierreBeucher Apr 26, 2026
66c2465
dev: disabled rate limit post Scaleway start
PierreBeucher Apr 26, 2026
87e543d
Merge pull request #373 from PierreBeucher/release-prepare
PierreBeucher Apr 26, 2026
93c80c6
chore: prepare release 0.45.0 - update version in package files and s…
PierreBeucher Apr 26, 2026
7653f16
chore(release-0.45.0): release 0.45.0
PierreBeucher Apr 26, 2026
3a61f6d
Merge pull request #374 from PierreBeucher/release-please--branches--…
PierreBeucher Apr 26, 2026
99c019c
Merge pull request #375 from PierreBeucher/release-0.45.0
PierreBeucher Apr 26, 2026
7eddc88
Merge pull request #359 from alexjurkiewicz/fix/pnpm-home-non-root-user
PierreBeucher May 2, 2026
260b691
chore: prepare release 0.45.1 - update version in package files and s…
PierreBeucher May 3, 2026
b6ead3e
chore(release-0.45.1): release 0.45.1
PierreBeucher May 3, 2026
a85a516
Merge pull request #377 from PierreBeucher/release-please--branches--…
PierreBeucher May 3, 2026
b6c874f
Merge pull request #378 from PierreBeucher/release-0.45.1
PierreBeucher May 3, 2026
c4c32e0
fix: nvidia devices permissions in container
PierreBeucher May 10, 2026
d409d83
Merge pull request #382 from PierreBeucher/fix-nvidia-permissions
PierreBeucher May 10, 2026
8377d40
dev: build before dev push
PierreBeucher May 10, 2026
034124d
Merge pull request #383 from PierreBeucher/fix-nvidia-permissions
PierreBeucher May 10, 2026
e3a547c
chore: prepare release 0.45.2 - update version in package files and s…
PierreBeucher May 10, 2026
3e9b9e7
chore(release-0.45.2): release 0.45.2
PierreBeucher May 10, 2026
08f2992
Merge pull request #384 from PierreBeucher/release-please--branches--…
PierreBeucher May 10, 2026
eb34fa3
Merge pull request #385 from PierreBeucher/release-0.45.2
PierreBeucher May 10, 2026
cde55c1
dev: fix pnpm version
PierreBeucher May 10, 2026
24f6a4a
Merge pull request #386 from PierreBeucher/fix-pnpm-version
PierreBeucher May 10, 2026
a312708
fix(sunshine): hide Prism Launcher from Moonlight until installed
Feb 3, 2026
1cfe9da
feat(sunshine): add App Installer to Moonlight
Feb 3, 2026
d2b6106
feat(sunshine): improve App Installer UX and use jq for JSON handling
Feb 4, 2026
da93e19
chore: prepare release 0.45.3 - update version in package files and s…
May 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .ai/history/2025-01-28-root-disk-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Root Disk Snapshot Feature Implementation

## Summary

Implementing root disk snapshotting for Scaleway and Linode. On initial deploy (after provision and configure), a snapshot of the root disk is created capturing the configured system (NVIDIA drivers, Cloudy Pad, etc.). This snapshot/image is used for subsequent instance starts.

## Issues

### Initial provision fails with "no image ID provided" even with rootDiskSnapshot enabled (fixed)

The check in `doMainProvision` was too strict - it required an image ID to already exist, but during initial deploy the root disk snapshot is created _after_ provision and configure. Fixed by also checking `rootDiskSnapshot.enable` - if enabled, the snapshot will be created at the end of deploy.

### Root disk ID is cleared before snapshot can be created (fixed)

In `deploy()`, calling `doStop()` triggers the full stop flow which sets `instanceServerState: absent` and deletes the server, also clearing the `rootDiskId` from provision output. But we need the `rootDiskId` to create the snapshot. Fixed by using `runner.stop()` directly instead of `doStop()` to just stop the instance without triggering server deletion.

### Block Snapshot cannot be used to boot a server - need Instance Image (fixed)

The root disk snapshot was creating a Scaleway **Block Snapshot** (`scw.block.Snapshot`), but the `Server` resource's `image` field expects an **Instance Image** (`scw.instance.Image`) which is a different resource type. Block Snapshots are for volume backup/restore, while Instance Images are bootable images for creating servers.

Additionally, Instance Snapshots (`scw.instance.Snapshot`) only work with Instance local volumes (`l_ssd`), NOT with SBS Block Storage volumes (`sbs_volume`). Since our root volume uses SBS for better reliability, we can't use Instance Snapshots/Images.

**Final Solution**: Use Block Snapshots and boot from a volume created from the snapshot:
1. Create a Block Snapshot (`scw.block.Snapshot`) of the root SBS volume
2. On server recreation, create a Block Volume from the snapshot
3. Use that volume as the server's root volume via `rootVolume.volumeId`

This required updating:
- `root-snapshot.ts`: Create Block Snapshot (not Instance Snapshot)
- `main.ts`: Add `rootVolume.snapshotId` parameter; when provided, create Block Volume from snapshot and use as root volume
- `provisioner.ts`: Pass `rootDiskSnapshotId` to `rootDisk.snapshotId` config (not `imageId`)

## Testing Log

### Test 1: Full lifecycle test - Create, Stop x2, Start x2

Commands used:
```bash
CLOUDYPAD_SKIP_CONFIGURATION=true npx tsx src/cli/main.ts create scaleway \
--name root-disk-snapshot-test \
--project-id "02d02f86-9414-4161-b807-efb2bd22d266" \
--region fr-par --zone fr-par-2 --instance-type L4-1-24G \
--root-disk-size 30 --data-disk-size 100 \
--streaming-server sunshine --sunshine-user sunshine --sunshine-password 'sunshine!' \
--autostop true --autostop-timeout 300 \
--yes --overwrite-existing --skip-pairing \
--data-disk-snapshot-enable \
--root-disk-snapshot-enable \
--delete-instance-server-on-stop

CLOUDYPAD_SKIP_CONFIGURATION=true npx tsx src/cli/main.ts stop root-disk-snapshot-test --wait
CLOUDYPAD_SKIP_CONFIGURATION=true npx tsx src/cli/main.ts stop root-disk-snapshot-test --wait
CLOUDYPAD_SKIP_CONFIGURATION=true npx tsx src/cli/main.ts start root-disk-snapshot-test
CLOUDYPAD_SKIP_CONFIGURATION=true npx tsx src/cli/main.ts start root-disk-snapshot-test
```

Results:
1. **Create** - ✅ Created instance, created root disk Block Snapshot at end of deploy
2. **Stop #1** - ✅ Created data disk snapshot, deleted server and data disk
3. **Stop #2** - ✅ No-op (already stopped)
4. **Start #1** - ✅ Created root volume from snapshot, created data disk from snapshot, created server
5. **Start #2** - ✅ No-op (already running)


64 changes: 64 additions & 0 deletions .ai/history/2026-01-28-data-disk-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Data Disk Snapshot Feature Implementation

## Summary

Successfully implemented data disk snapshotting for Scaleway to reduce costs. On stop, data volume is snapshot then deleted. On start, data volume is restored from snapshot.

## Major Changes

### Initial Implementation

- Added `--data-disk-snapshot-enable` CLI flag to Scaleway create command to enable the feature
- Added `dataDiskSnapshotId` field to CommonProvisionOutput schema for tracking snapshot IDs
- Created dedicated `ScalewayDataDiskSnapshotPulumiClient` Pulumi stack to manage snapshot creation separately from main infrastructure
- Modified Scaleway main Pulumi stack to support `dataDiskSnapshotId` (restore from snapshot) and `noDataDisk` (delete volume) options

### Refactoring: State-based provisioner control

Refactored the manager/provisioner interface to use state-based inputs instead of dedicated methods:

- Changed `dataDiskSnapshotEnable: boolean` to `dataDiskSnapshot: { enable: boolean }` for extensibility
- Added `runtime` object to CommonProvisionInputV1Schema to clearly separate user config from runtime state:
- `runtime.enableInstanceServer?: boolean` - if true, server should exist; if false, delete server
- `runtime.dataDiskState?: "live" | "snapshot"` - "live" = disk should exist, "snapshot" = create snapshot then delete disk
- Removed dedicated methods from InstanceProvisioner interface (`destroyInstanceServer()`, `createDataDiskSnapshot()`, `destroyDataDiskSnapshot()`)
- Manager now updates `runtime` state before calling `provision()`:
- On stop: sets `enableInstanceServer: false` (if deleteServerOnStop), `dataDiskState: "snapshot"` (if snapshot enabled)
- On start: sets `enableInstanceServer: true`, `dataDiskState: "live"`
- Provisioner reads runtime state from input and acts accordingly (no conditional logic in manager)
- Updated Scaleway and Dummy provisioners to handle runtime state in `doProvision()`

### Refactoring: Split provisioner into dataSnapshotProvision and mainProvision

Split the provisioner responsibilities more clearly between manager and provisioner:

- **InstanceProvisioner interface** now has two methods:
- `dataSnapshotProvision()` - manages snapshot stack based on `runtime.dataDiskState`
- `mainProvision()` - manages main infrastructure (server, disks) based on runtime flags
- **Manager `doProvision()`** now calls both methods in sequence:
1. First calls `dataSnapshotProvision()` - updates snapshot state
2. Rebuilds provisioner with updated state (includes snapshot output)
3. Calls `mainProvision()` - handles server/disk operations
- **Scaleway provisioner** implements split:
- `doDataSnapshotProvision()`: creates snapshot if `dataDiskState=snapshot`, else returns current state
- `doMainProvision()`: runs main Pulumi stack, destroys snapshot stack after disk restoration
- **Other providers** (AWS, Azure, GCP, Paperspace, Linode, SSH, Dummy) implement no-op for `doDataSnapshotProvision()`, return current output
- **Added `CLOUDYPAD_SKIP_CONFIGURATION=true`** env var support in manager to skip Ansible configuration during testing

## Issues

### Snapshot creation also deleted instance server (fixed)

When creating snapshot on stop, the provisioner was setting `noInstanceServer: true` which deleted the server even when `deleteInstanceServerOnStop` was not enabled. Fixed by only setting `noDataDisk: true` - server deletion is handled separately.

### Start didn't restore data disk from snapshot (fixed)

When `dataDiskSnapshot.enable` was true but `deleteInstanceServerOnStop` was false, the start flow didn't trigger provision to restore the data disk from snapshot. Fixed by adding a check in `start()` to call `doProvision()` when `dataDiskSnapshot.enable` is enabled.

### Snapshot update failed on second stop (fixed)

On second stop, the snapshot Pulumi stack already existed and Pulumi tried to UPDATE the snapshot with a new volumeId. The Scaleway provider has issues with this update (`scaleway-sdk-go: invalid argument(s): name,tags is required`). Fixed by destroying the snapshot stack after restoring from it during `start` provision - this ensures each stop creates a fresh snapshot rather than trying to update an existing one.

### Constants for data disk state values

Added `DATA_DISK_STATE_LIVE` and `DATA_DISK_STATE_SNAPSHOT` constants to `src/core/const.ts` to avoid magic strings in comparisons across manager and provisioner code.
97 changes: 97 additions & 0 deletions .ai/history/2026-01-29-10-48-Azure-Advanced-Features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Azure Advanced Features Implementation

Implement enableServerDeletionOnStop, Data Disk with Snapshots, and Base Image for Azure provider.

## Initial iteration

> let's implement
> - enableServerDeletionOnStop option
> - Data Disk with Data Disk Snapshot
> - Base Image
>
> for Azure provider. It's currently implemented for Scaleway, Linode and AWS, take it as example. Write history as you go and include @.cursor/rules/ai-history.mdc
>
> Follow architecture @.cursor/rules/architecture.mdc
>
> - Update state inputs accordingly
> - Create Pulumi stacks
> - Update provisionr accordingly
> - Update other provider code as needed
> - Update CLI flags as needed to add flags related to data disk, base image, etc while remainign consistent
> - Avoid if possible to update core/ - unless necessary or you think it's logic to. Explain your choice in history if this happens
>
> Workflow:
>
> 1. First implement everything then run quickly check it compiles, run test task test-unit and npx tsx src/cli/main --version to avoid obvious error
>
> 2. Once initial implementation is done, start iterating on Azure, directly. At this point don't check compilation / unit test / cli every time, you'll use Azure integ test directly:
> - Test on Azure directly: use test test/integ/stable/core/providers/azure/lifecycle.spec.ts run via commands such as ```npx mocha --config test/integ/.mocharc.json ./test/integ/stable/core/providers/azure/lifecycle.spec.ts --grep "destroy instance"```
> - Update the test to check that our new features works as expected:
> - check server is deleted
> - check base image matches state output
> - check data disk matches state output
> - When checking Azure infra, update our internal Azure client with related functions
> - Update timeouts if needed
> - Test is written sequentially. Each it() is a specific step, use --grep to run a specific step and adapt: run a step, if it fails fix and retry the same step again without going all over again
> - It's possible you may need to destroy instance and start afresh. Only destroy the instance if really necessary as current state became inconsistent. To destroy instance, use the destroy instance it()
>
> Important note:
>
> - Remain consitent with other provides in CLI flags, stack names, function names, etc.
> - Include @.cursor/rules/ai-history.mdc

### Code changes

Implemented three major features for Azure provider following patterns from AWS, Scaleway, and Linode:

1. **State schema updated**: Added fields for rootDiskId, dataDiskId, dataDiskSnapshotId, baseImageId, dataDiskSizeGb, imageId, and deleteInstanceServerOnStop
2. **Created Pulumi stacks**:
- `pulumi/data-volume-snapshot.ts` - Creates Azure managed disk snapshots
- `pulumi/base-image-snapshot.ts` - Creates Azure images from disk snapshots
- Reorganized `pulumi.ts` → `pulumi/main.ts`
3. **Updated main Pulumi stack**: Added support for data disks, custom images, and runtime state control (instanceServerState, dataDiskState)
4. **Updated provisioner**: Implemented doDataSnapshotProvision, doBaseImageSnapshotProvision, and updated destroy logic
5. **Updated CLI**: Added new flags for --root-disk-size, --data-disk-size, --image-id, --delete-instance-server-on-stop, --data-disk-snapshot-enable, --base-image-snapshot-enable, --keep-base-image-on-deletion
6. **Updated SDK client**: Added getImage(), getDisk(), getSnapshot() methods for testing/verification
7. **Updated runner**: Fixed vmName undefined handling with proper error messages

### Issues encountered

**Compilation errors with vmName becoming optional**: Made vmName optional in AzureProvisionOutputV1 to support server deletion on stop, which caused type errors in runner.ts. Fixed by adding proper undefined checks with meaningful error messages in the runner's `getVmName()` method.

**Parallel GCP/AWS work causing compile issues**: Another agent working on GCP features in parallel caused compilation errors. Waited for changes to settle before continuing. Azure-specific errors are all fixed.

**Azure-specific implementation notes**:
- Azure uses managed disks with full resource IDs (not short IDs like AWS)
- Azure Image creation requires: Disk → Snapshot → Image (2-step process)
- Data disks are attached via VM's `storageProfile.dataDisks` array with LUN
- VM deletion on stop requires conditional Pulumi resource creation based on `instanceServerState` flag
- Disk snapshots use Azure's managed disk snapshot API, not block storage snapshots

## Summary

Initial implementation complete and ready for integration testing. All core features implemented:

✅ State schema updated with new fields
✅ Pulumi stacks created for data disk snapshots and base images
✅ Main Pulumi stack updated with data disk and runtime state support
✅ Provisioner implements all snapshot methods
✅ CLI flags added for all new features
✅ SDK client methods added for verification
✅ Integration tests enhanced to verify new features
✅ Azure-specific errors fixed

**Next steps for integration testing**:
1. Wait for GCP/AWS parallel work to complete (compilation blocked)
2. Run integration test: `npx mocha --config test/integ/.mocharc.json ./test/integ/stable/core/providers/azure/lifecycle.spec.ts`
3. Use `--grep` to run specific test steps if failures occur
4. Iterate on fixes as needed

**Testing approach**:
- Test each feature independently using --grep
- Start with "initialize instance state" and "deploy instance"
- Verify data disk, base image, and snapshots in Azure portal
- Test stop/start cycle to verify server deletion and recreation
- Only destroy instance if state becomes inconsistent


Loading
Loading