- Rename .claude/rules/hive.md → spinifex.md - Update CLAUDE.md component description - Rename doc directories: hive-admin-cli → spinifex-admin-cli, etc. - Rename doc files: hive-cluster-stats, hive-code-consolidation, etc. - Bulk update all markdown files across docs/ tree - Update internal docs: CLUSTER-INSTALL, TESTING, DEVNOTES, BRANCHES
96 KiB
Command Implementation Matrix
Spinifex Admin CLI
Platform management commands not exposed via the AWS gateway API. These are CLI-only commands.
Service Management
Service lifecycle commands for starting, stopping, and checking status of all Spinifex cluster services. Each service subcommand supports start, stop, and status operations.
| Command | Flags | Prerequisites | Basic Logic | Status |
|---|---|---|---|---|
spx service predastore start |
--port (default: 8443), --host (default: 0.0.0.0), --base-path, --config-path, --debug, --tls-cert, --tls-key, --backend (distributed/filesystem, default: distributed), --node-id (default: 0), --pprof, --pprof-output |
TLS cert/key, base-path, config-path required | Creates predastore service instance with S3-compatible storage backend → starts service | DONE |
spx service predastore stop |
— | Predastore must be running | Stops the predastore service | DONE |
spx service predastore status |
— | None | Reports predastore service status | DONE |
spx service viperblock start |
--s3-host (default: 0.0.0.0:8443), --s3-bucket (default: predastore), --s3-region (default: ap-southeast-2), --plugin-path (default: /opt/spinifex/lib/nbdkit-viperblock-plugin.so) |
--config required, plugin-path must exist on disk |
Loads cluster config → connects to NATS and Predastore → starts viperblock block storage service with NBD plugin | DONE |
spx service viperblock stop |
— | Viperblock must be running | Stops the viperblock service | DONE |
spx service viperblock status |
— | None | Reports viperblock service status | DONE |
spx service nats start |
--port (default: 4222), --host (default: 0.0.0.0), --debug, --data-dir, --jetstream |
None | Starts embedded NATS server with optional JetStream | DONE |
spx service nats stop |
— | NATS must be running | Stops the NATS service | DONE |
spx service nats status |
— | None | Reports NATS service status | DONE |
spx service spinifex start |
--wal-dir |
--config required |
Loads cluster config → starts spinifex daemon (VM orchestration, NATS subscriptions, health endpoint) | DONE |
spx service spinifex stop |
— | Spinifex daemon must be running | Stops the spinifex daemon service | DONE |
spx service spinifex status |
— | None | Reports spinifex daemon service status | DONE |
spx service awsgw start |
--host (default: 0.0.0.0:9999), --tls-cert, --tls-key, --debug |
--config required |
Loads cluster config → starts AWS-compatible gateway with SigV4 auth, IAM policy enforcement, TLS | DONE |
spx service awsgw stop |
— | AWS gateway must be running | Stops the AWS gateway service | DONE |
spx service awsgw status |
— | None | Reports AWS gateway service status | DONE |
spx service spinifex-ui start |
--port (default: 3000), --host (default: 0.0.0.0), --tls-cert, --tls-key |
None | Starts embedded web UI server serving the React frontend. Aliases: ui, spinifexui |
DONE |
spx service spinifex-ui stop |
— | spinifex-ui must be running | Stops the spinifex-ui service | DONE |
spx service spinifex-ui status |
— | None | Reports spinifex-ui service status | DONE |
spx service vpcd start |
— | --config required, OVN/OVS installed |
Loads cluster config → starts VPC daemon (subscribes to vpc.* NATS events, translates to OVN logical switches/ports/routers) |
DONE |
spx service vpcd stop |
— | vpcd must be running | Stops the vpcd service | DONE |
spx service vpcd status |
— | None | Reports vpcd service status | DONE |
Cluster Inspection
Operational commands for inspecting cluster state. These fan out NATS requests to all nodes and aggregate responses.
| Command | Flags | Prerequisites | Basic Logic | Output Columns | Status |
|---|---|---|---|---|---|
spx get nodes |
--timeout (default: 3s) |
Cluster must be running (NATS) | Loads config → publishes to spinifex.node.status fan-out topic → collects responses within timeout → merges config-known nodes with NATS responders → nodes that don't respond shown as NotReady |
NAME, STATUS, IP, REGION, AZ, UPTIME, VMs, SERVICES | DONE |
spx get vms |
--timeout (default: 3s) |
Cluster must be running (NATS) | Publishes to spinifex.node.vms fan-out topic → collects VM info from all nodes → sorts by node then instance ID → prints table. Alias: spx get instances |
INSTANCE, STATUS, TYPE, VCPU, MEM, NODE, IP, AGE | DONE |
Resource Monitoring
| Command | Flags | Prerequisites | Basic Logic | Output | Status |
|---|---|---|---|---|---|
spx top nodes |
--timeout (default: 3s) |
Cluster must be running (NATS) | Publishes to spinifex.node.status fan-out topic → collects CPU/memory usage per node → aggregates instance type capacity across all nodes → prints two tables: per-node resource usage and cluster-wide instance type availability |
Table 1: NAME, CPU (used/total), MEM (used/total), VMs. Table 2: INSTANCE TYPE, AVAILABLE, VCPU, MEMORY | DONE |
Cluster Initialization
| Command | Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|
spx admin init |
--nodes, --node, --bind, --port, --region, --az, --cluster-name, --cluster-bind, --cluster-routes, --predastore-nodes, --services, --formation-timeout, --force |
None (first-time setup) | Generates root IAM credentials (AKIA-prefixed access key + secret) → creates master.key (AES-256, 32 bytes, 0600) → writes bootstrap.json (consumed on first start) → generates CA + server TLS certificates → creates NATS config with auth token → writes spinifex.toml, awsgw.toml, predastore.toml → configures AWS CLI spx profile → creates directory structure under ~/spinifex/ |
1. Init creates all config files 2. Root credentials printed once 3. master.key is 32 bytes, mode 0600 4. bootstrap.json consumed on first start 5. --force re-initializes existing config6. AWS CLI profile spx auto-configured |
DONE |
spx admin join |
--host (required), --node (required), --bind, --port, --region, --az, --cluster-bind, --cluster-routes, --data-dir, --services |
Leader node must be running | Connects to leader node → retrieves cluster configuration → configures local node to join cluster and participate in distributed operations | 1. Join existing cluster 2. Missing host (error) 3. Missing node name (error) |
DONE |
Cluster Operations
| Command | Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|
spx admin cluster shutdown |
— | Cluster must be running | Gracefully shuts down the entire cluster | 1. Shutdown running cluster 2. All nodes stop cleanly |
DONE |
Account Management
| Command | Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|
spx admin account create |
--name |
Cluster must be running (NATS), master key must exist | Connects to NATS → CAS loop on spinifex-account-counter:next_id for sequential 12-digit ID → creates Account record in spinifex-accounts KV → creates admin user in new account → creates access key for admin → creates AdministratorAccess policy (Action:, Resource:) → attaches policy → prints credentials |
1. Create first account (ID 000000000001) 2. Sequential IDs (000000000002, etc.) 3. Admin user has full access 4. Credentials printed once |
DONE |
spx admin account list |
— | Cluster must be running (NATS), master key must exist | Connects to NATS → IAMService.ListAccounts() → prints table with Account ID, Name, Status, Created | 1. List all accounts 2. Empty list (only global account) |
DONE |
Image Management
| Command | Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|
spx admin images import |
--name |
Cluster must be running, image file must exist locally | Imports a local disk image (QCOW2) as an AMI → uploads to Predastore S3 → registers as available AMI | 1. Import valid QCOW2 image 2. Image appears in describe-images |
DONE |
spx admin images list |
— | None | Lists available OS images that can be imported or downloaded | 1. List available images | DONE |
AWS Commands
EC2 - Instance Management
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
run-instances |
--image-id, --instance-type, --count (Min/MaxCount), --key-name, --user-data, --subnet-id (auto-creates ENI, assigns private IP), --block-device-mappings (DeviceName, VolumeSize, VolumeType, Iops, DeleteOnTermination) |
--security-group-ids, --placement, --tag-specifications, --dry-run, --client-token, --disable-api-termination, --ebs-optimized, --iam-instance-profile, --network-interfaces, --private-ip-address, --monitoring, --credit-specification, --cpu-options, --metadata-options, --launch-template, --hibernate-options |
describe-images (AMI must exist), create-key-pair (optional), VPC/SG (optional) |
Gateway parses AWS query → NATS ec2.runinstances → daemon creates QEMU/KVM VM with viperblock-backed root volume via NBD → if SubnetId provided, auto-creates ENI with private IP → cloud-init injects user-data/keys → returns reservation with instance ID |
1. Launch with valid AMI and key pair 2. Launch with invalid AMI ID (error) 3. Launch with block device mappings (custom volume size) 4. Launch multiple instances (MinCount/MaxCount) 5. Launch with subnet-id (auto-creates ENI) 6. Invalid instance type returns error |
DONE |
describe-instances |
--instance-ids |
--filters, --max-results, --next-token, --dry-run |
None | Gateway fans out NATS ec2.DescribeInstances to all nodes (no queue group) → each daemon returns local instances → gateway aggregates and returns combined list |
1. Describe all instances (no filter) 2. Describe by instance ID 3. Describe with filters (e.g. instance-state-name) 4. Instance not found returns empty set 5. Multi-node aggregation returns instances from all nodes |
DONE |
start-instances |
--instance-ids |
--dry-run, --force |
run-instances (instance must exist in stopped state) |
Gateway sends NATS ec2.cmd.{instance-id} → daemon restarts stopped QEMU process with same config → state transitions stopped→pending→running |
1. Start a stopped instance 2. Start already-running instance (error: IncorrectInstanceState) 3. Start with invalid instance ID 4. Verify volumes re-mount on start |
DONE |
stop-instances |
--instance-ids |
--force, --hibernate, --dry-run |
run-instances (instance must be running) |
Gateway sends NATS to target node → daemon issues QMP system_powerdown for graceful shutdown → monitors heartbeat until QEMU exits → state transitions running→stopping→stopped |
1. Graceful stop of running instance 2. Force stop (kills QEMU process) 3. Stop already-stopped instance (error) 4. Verify ~30s heartbeat detection |
DONE |
terminate-instances |
--instance-ids, DeleteOnTermination (per-volume flag, default true) |
--dry-run |
run-instances (instance must exist) |
Gateway sends NATS to target node → daemon kills QEMU process → cleans up NBD mounts → deletes volumes with DeleteOnTermination=true via volumeService.DeleteVolume() (S3 cleanup of vol/, vol-efi/, vol-cloudinit/) → internal volumes (EFI, cloud-init) always cleaned up via ebs.delete NATS → volumes with DeleteOnTermination=false left in available state → state→terminated |
1. Terminate running instance 2. Terminate stopped instance 3. Terminate with DeleteOnTermination=true deletes volumes 4. Terminate with DeleteOnTermination=false preserves volumes 5. Terminate already-terminated (idempotent) 6. Internal volumes (EFI, cloud-init) always cleaned up 7. Invalid instance ID |
DONE |
reboot-instances |
— | --instance-ids, --dry-run |
run-instances (instance must be running) |
Gateway sends NATS to target node → daemon issues QMP system_reset → instance reboots without stopping |
1. Reboot running instance 2. Reboot stopped instance (error) 3. Verify instance stays in running state after reboot |
NOT STARTED (parser test exists) |
describe-instance-types |
--filters (capacity filter only) |
--instance-types, --max-results, --next-token, --dry-run, all other filters |
None | Gateway fans out NATS ec2.DescribeInstanceTypes to all nodes → each daemon reports supported types (t3.micro/small/medium/large) with vCPU/memory specs → gateway deduplicates and returns |
1. List all instance types 2. Filter by specific type 3. Filter with capacity=true shows available slots4. Verify vCPU/memory specs match hardware |
DONE |
modify-instance-attribute |
--instance-id, --instance-type, --user-data |
--disable-api-termination, --ebs-optimized, --source-dest-check, --instance-initiated-shutdown-behavior, --block-device-mappings, --groups, --ena-support, --sriov-net-support |
Instance must be stopped (in NATS KV) | Gateway validates input (exactly one attribute per call, instance ID format) → NATS ec2.ModifyInstanceAttribute with spinifex-workers queue group → daemon loads stopped instance from JetStream KV → applies attribute change → writes back to KV → returns {} on success. InstanceType: updates vm.InstanceType, Config, and Instance fields; clears StateReason (enables recovery from instance-type-missing bug). UserData: stores decoded content in vm.UserData and re-encodes to base64 for RunInstancesInput (cloud-init on next start). No instance type pre-validation (matches AWS — invalid types accepted, fail at StartInstances time). |
1. Change instance type while stopped 2. Change user data while stopped 3. Modify running instance (error: NotFound — running instances not in KV) 4. Instance not found (error: InvalidInstanceID.NotFound) 5. Instance not stopped (error: IncorrectInstanceState) 6. Invalid instance type accepted (fails on start with InsufficientInstanceCapacity) 7. StateReason cleared on type change (recovery from capacity-unavailable) 8. Missing/malformed instance ID (error: InvalidInstanceID.Malformed) 9. No attribute set (error: InvalidParameterValue) 10. Multiple attributes in one call (error: InvalidParameterValue) |
DONE |
get-console-output |
--instance-id |
--latest (always returns latest), --dry-run |
Instance must be running on a node | Gateway sends NATS ec2.{instanceId}.GetConsoleOutput (per-instance topic, routed to owning node) → daemon reads console log file from disk → returns last 64KB base64-encoded with timestamp. Always available regardless of serial console access setting (matches AWS behavior). |
1. Get output from running instance 2. Empty log file returns empty output 3. Instance not found (error: InvalidInstanceID.NotFound) |
DONE |
monitor-instances |
— | --instance-ids |
Instance must exist | Enable basic monitoring (CPU, disk, network) → store metrics in NATS KV → return monitoring state | 1. Enable monitoring 2. Verify monitoring state in describe-instances |
NOT STARTED |
unmonitor-instances |
— | --instance-ids |
Instance must exist | Disable detailed monitoring → revert to basic monitoring → return monitoring state | 1. Disable monitoring 2. Verify monitoring state reverts in describe-instances |
NOT STARTED |
EC2 - Key Pair Management
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-key-pair |
--key-name, --key-type (rsa/ed25519) |
--key-format (pem/ppk), --tag-specifications, --dry-run |
None | NATS ec2.CreateKeyPair → daemon generates SSH keypair → stores public key in Predastore S3 (/bucket/ec2/{name}.pub) → returns private key material (one-time) |
1. Create RSA key pair 2. Create ED25519 key pair 3. Duplicate key name (error: InvalidKeyPair.Duplicate) 4. Verify key material returned only on creation |
DONE |
describe-key-pairs |
--key-names, --key-pair-ids |
--filters, --max-results, --dry-run |
None | NATS ec2.DescribeKeyPairs → daemon lists public keys from Predastore S3 → returns key names and fingerprints |
1. List all key pairs 2. Filter by key name 3. Filter by key pair ID 4. Non-existent key returns empty |
DONE |
delete-key-pair |
--key-name, --key-pair-id |
--dry-run |
Key must exist | NATS ec2.DeleteKeyPair → daemon deletes public key from Predastore S3 → returns success |
1. Delete existing key pair 2. Delete non-existent key (idempotent, no error) 3. Verify key no longer in describe-key-pairs |
DONE |
import-key-pair |
--key-name, --public-key-material |
--tag-specifications, --dry-run |
None | NATS ec2.ImportKeyPair → daemon stores provided public key in Predastore S3 → returns key name and fingerprint |
1. Import valid SSH public key 2. Import invalid key material (error) 3. Import duplicate name (error) 4. Verify imported key usable with run-instances |
DONE |
EC2 - AMI Image Management
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
describe-images |
--image-ids (format validation only), --owners (self, account ID, alias) |
--executable-users, --filters, --include-deprecated, --include-disabled, --max-results, --next-token, --dry-run |
None | NATS ec2.DescribeImages → daemon reads AMI metadata from Predastore S3 buckets (ami-*) → filters by ImageIds and Owners → returns image list with state, architecture, block device mappings |
1. List all images 2. Filter by image ID 3. Filter by owner (self/amazon) 4. Non-existent AMI returns empty 5. Verify metadata fields (architecture, state, rootDeviceName) |
DONE |
create-image |
--instance-id, --name, --description, --tag-specifications |
--no-reboot, --block-device-mappings, --dry-run |
Instance must exist (running or stopped) | Gateway validates input → NATS ec2.{instanceId}.CreateImage (per-instance topic) → daemon extracts root volume → snapshots via ebs.snapshot NATS (running) or offline S3 copy (stopped) → creates AMI metadata in S3 ({amiId}/config.json) → stores tags → returns ami-ID. Duplicate AMI name validation enforced. |
1. Create image from running instance 2. Create image from stopped instance 3. Invalid instance ID (error) 4. Duplicate AMI name (error) 5. Verify new AMI appears in describe-images 6. Launch new instance from created AMI |
DONE |
register-image |
— | --name, --description, --architecture, --root-device-name, --virtualization-type, --block-device-mappings, --boot-mode, --ena-support, --sriov-net-support, --tpm-support, --imds-support |
Image data must exist in S3 | Create AMI metadata entry in Predastore → assign ami-ID → set state to available → return ami-ID | 1. Register with valid metadata 2. Missing required name (error) 3. Verify registered image in describe-images |
NOT STARTED |
deregister-image |
— | --image-id, --dry-run |
AMI must exist, no instances using it | Mark AMI metadata as deregistered in S3 → AMI no longer available for new launches → existing instances unaffected | 1. Deregister existing AMI 2. Deregister non-existent AMI (error) 3. Verify deregistered AMI not in describe-images 4. Existing instances from AMI still run |
NOT STARTED |
copy-image |
— | --source-image-id, --source-region, --name, --description, --encrypted, --kms-key-id, --client-token, --copy-image-tags, --tag-specifications |
Source AMI must exist | Copy AMI data between regions/nodes in Predastore → create new AMI metadata with new ami-ID → return new ami-ID | 1. Copy image within same region 2. Copy non-existent image (error) 3. Verify copied image independent of source |
NOT STARTED |
import-image |
— | --disk-containers (Format, Url/S3Bucket+S3Key), --description, --architecture, --platform |
S3 bucket with disk image | Download disk image from S3 → convert format (VMDK/VHD/RAW→QCOW2) → create viperblock volume → register as AMI | 1. Import QCOW2 image 2. Import RAW image 3. Invalid format (error) 4. Verify imported image launchable |
NOT STARTED |
describe-image-attribute |
— | --image-id, --attribute, --dry-run |
AMI must exist | Read specific attribute from AMI metadata in S3 → return attribute value | 1. Get description attribute 2. Get launch permission 3. Invalid attribute name (error) |
NOT STARTED |
modify-image-attribute |
— | --image-id, --launch-permission, --description, --operation-type, --user-ids, --user-groups, --organization-arns, --dry-run |
AMI must exist | Update specific attribute in AMI metadata → persist to S3 | 1. Add launch permission 2. Modify description 3. Invalid AMI (error) |
NOT STARTED |
reset-image-attribute |
— | --image-id, --attribute, --dry-run |
AMI must exist | Reset attribute to default value in AMI metadata | 1. Reset launch permission to owner-only 2. Invalid AMI (error) |
NOT STARTED |
EC2 - Volume (EBS) Management
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
describe-volumes |
--volume-ids (fast-path lookup), DeleteOnTermination (from persisted VolumeMetadata) |
--filters, --max-results, --next-token, --dry-run |
None | NATS ec2.DescribeVolumes → daemon queries viperblock for volume metadata → returns volume list with state, size, attachments, type, DeleteOnTermination flag |
1. List all volumes 2. Filter by volume ID 3. Filter by attachment state 4. Non-existent volume returns empty 5. DeleteOnTermination reflects persisted value |
DONE |
modify-volume |
--volume-id, --size, --volume-type, --iops |
--throughput, --dry-run, --multi-attach-enabled |
Volume must exist | NATS ec2.ModifyVolume → daemon sends resize request to viperblock → NBD does not support live resize, instance must be stopped → returns modification state |
1. Increase volume size 2. Modify volume type 3. Decrease size (error - not supported) 4. Modify attached volume (requires stop/start) |
DONE |
create-volume |
--size, --availability-zone, --volume-type (gp3 only), --snapshot-id (creates volume from snapshot) |
--iops (hardcoded 3000), --encrypted (hardcoded false), --throughput, --tag-specifications |
Valid AZ configured via spinifex init |
Gateway validates input → NATS ec2.CreateVolume → daemon generates vol-ID via viperblock → creates volume (empty or from snapshot) of specified size → persists config.json to Predastore S3 → returns vol-ID with state=available |
1. Create 80GB gp3 volume 2. Boundary sizes (1 GiB min, 16384 GiB max) 3. Invalid AZ (error) 4. Verify volume in describe-volumes 5. Unsupported volume type (error - only gp3) 6. Size out of range (error) 7. Create from snapshot |
DONE |
delete-volume |
--volume-id |
--dry-run |
Volume must exist and be detached (state=available) | Gateway validates vol- prefix → NATS ec2.DeleteVolume → daemon confirms state=available and no AttachedInstance → NATS ebs.delete to viperblockd (stops nbdkit/WAL) → deletes S3 objects under vol-id/, vol-id-efi/, vol-id-cloudinit/ → returns success |
1. Delete detached volume 2. Delete attached volume (error: VolumeInUse) 3. Delete non-existent volume (error: InvalidVolume.NotFound) 4. Verify volume gone from describe-volumes 5. Malformed volume ID (error: InvalidVolumeID.Malformed) 6. Double delete (idempotent NotFound) |
DONE |
attach-volume |
--volume-id, --instance-id, --device (optional, auto-assigns /dev/sd[f-p]) |
--dry-run |
Volume must exist (available), instance must exist (running) | Gateway sends to ec2.cmd.{instanceId} → daemon validates volume (Predastore) → ebs.mount via NATS (viperblock starts NBD server) → QMP blockdev-add (nbd-{volId}) → QMP device_add (virtio-blk-pci, vdisk-{volId}) → three-phase rollback on failure → update EBSRequests + BlockDeviceMappings → persist to JetStream + Predastore → respond with VolumeAttachment |
1. Attach volume to running instance 2. Auto-assign device name 3. Attach already-attached volume (VolumeInUse) 4. Attach to non-existent instance (InvalidInstanceID.NotFound) 5. Attach to stopped instance (IncorrectInstanceState) 6. Volume not found (InvalidVolume.NotFound) 7. All device slots full (AttachmentLimitExceeded) 8. Volume persists across stop/start |
DONE |
detach-volume |
--volume-id, --instance-id (optional, resolved via DescribeVolumes), --device (optional cross-check), --force |
--dry-run |
Volume must be attached, instance must be running | Gateway resolves InstanceId if omitted (via DescribeVolumes) → sends to ec2.cmd.{instanceId} → daemon validates (running, attached, not boot/EFI/CloudInit, device match) → three-phase hot-unplug: QMP device_del (force continues on failure) → QMP blockdev-del (abort if fails, preserves state to prevent double-attach) → ebs.unmount via NATS (best-effort) → remove from EBSRequests + BlockDeviceMappings → update volume metadata to available → persist state → respond with VolumeAttachment (state=detaching) |
1. Detach with explicit InstanceId 2. Detach without InstanceId (gateway resolution) 3. Detach with correct --device cross-check 4. Missing VolumeId (InvalidParameterValue) 5. Volume not attached (IncorrectState) 6. Nonexistent volume (InvalidVolume.NotFound) 7. Nonexistent instance (InvalidInstanceID.NotFound) 8. Instance not running (IncorrectInstanceState) 9. Device mismatch (InvalidParameterValue) 10. Boot volume protection (OperationNotPermitted) 11. Force flag (continues past device_del failure) 12. Volume reusability (re-attach after detach) |
DONE |
describe-volume-status |
--volume-ids |
--filters, --max-results, --next-token, --dry-run |
None | Gateway validates vol- prefix → NATS ec2.DescribeVolumeStatus → daemon fetches VolumeConfig from Predastore S3 (parallel for specific IDs, sequential list-all for no IDs) → builds VolumeStatusItem per volume (status=ok, io-enabled=passed, io-performance=not-applicable) → returns InvalidVolume.NotFound for missing explicit IDs → skips internal sub-volumes (-efi, -cloudinit) |
1. List all volume statuses 2. Filter by specific volume IDs (fast path) 3. Non-existent volume ID returns InvalidVolume.NotFound 4. Invalid volume ID format (InvalidVolume.Malformed) 5. Internal sub-volumes excluded from listing 6. Nil/empty input defaults to all volumes |
DONE |
describe-volumes-modifications |
— | --volume-ids, --filters, --max-results |
None | Query pending/completed volume modifications → return modification state, progress, original/target size | 1. Check in-progress modification 2. Check completed modification 3. No modifications returns empty |
NOT STARTED |
EC2 - Snapshot Management
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-snapshot |
--volume-id, --description, --tag-specifications |
--dry-run |
Volume must exist in Predastore | Gateway validates vol- prefix → NATS ec2.CreateSnapshot → daemon reads VolumeConfig from Predastore → generates snap-ID via viperblock → stores SnapshotConfig (metadata-only, points to source volume) as completed → returns Snapshot with state=completed, progress=100% |
1. Snapshot from valid volume 2. Missing volume ID (InvalidParameterValue) 3. Volume not found (InvalidVolume.NotFound) 4. Invalid volume ID format (InvalidVolume.Malformed) 5. Snapshot with description 6. Snapshot with tag specifications |
DONE |
create-snapshots |
— | --instance-specification, --description, --tag-specifications |
Instance must exist, instance-volume attachment tracking | Create snapshots of all volumes attached to instance → return list of snapshot IDs. Blocked: requires instance-volume attachment tracking to discover which volumes to snapshot. | 1. Snapshot all volumes on instance 2. Instance with no volumes |
NOT STARTED |
delete-snapshot |
--snapshot-id |
--dry-run |
Snapshot must exist | Gateway validates snap- prefix → NATS ec2.DeleteSnapshot → daemon verifies snapshot exists in Predastore → lists and deletes all objects under snapshot prefix → returns success |
1. Delete existing snapshot 2. Delete non-existent snapshot (InvalidSnapshot.NotFound) 3. Missing snapshot ID (InvalidParameterValue) 4. Invalid snapshot ID format (InvalidSnapshot.Malformed) |
DONE |
describe-snapshots |
--snapshot-ids |
--owner-ids, --filters, --max-results, --dry-run |
None | Gateway validates snap- prefix on IDs → NATS ec2.DescribeSnapshots → daemon lists snap- prefixed objects in Predastore → reads SnapshotConfig for each → filters by snapshot ID if specified → returns snapshot list |
1. List all snapshots 2. Filter by snapshot ID 3. Empty snapshot list 4. Invalid snapshot ID format (InvalidSnapshot.Malformed) |
DONE |
copy-snapshot |
--source-snapshot-id, --source-region, --description |
--encrypted, --dry-run |
Source snapshot must exist | Gateway validates snap- prefix + source region → NATS ec2.CopySnapshot → daemon reads source SnapshotConfig → generates new snap-ID → copies metadata (preserves tags, description override) → stores as completed → returns new snapshot ID |
1. Copy within same region 2. Copy non-existent snapshot (InvalidSnapshot.NotFound) 3. Missing source ID (InvalidParameterValue) 4. Missing source region (MissingParameter) 5. Copy preserves tags 6. Copy with description override |
DONE |
EC2 - Tags
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-tags |
--resources, --tags (Key=,Value=) |
--dry-run |
None | Gateway validates input (resources non-empty, tags non-empty, keys non-empty) → NATS ec2.CreateTags → daemon merges tags into per-resource JSON in Predastore S3 (tags/{resourceId}.json) → return success |
1. Tag an instance 2. Tag multiple resources at once 3. Overwrite existing tag value 4. Empty resources list (error: MissingParameter) 5. Empty tags list (error: MissingParameter) 6. Empty tag key (error: InvalidParameterValue) |
DONE |
delete-tags |
--resources, --tags |
--dry-run |
None | Gateway validates input (resources non-empty) → NATS ec2.DeleteTags → daemon removes specified tag keys from resource JSON, or all tags if no keys specified → return success |
1. Delete specific tag by key 2. Delete all tags (no tags specified) 3. Empty resources list (error: MissingParameter) |
DONE |
describe-tags |
--filters (resource-id, resource-type, key, value) |
--max-results, --next-token, --dry-run |
None | NATS ec2.DescribeTags → daemon lists all tags/*.json from Predastore S3 → applies filters (resource-id, resource-type, key, value) → returns tag list with resource type auto-detected from ID prefix |
1. List all tags 2. Filter by resource-id 3. Filter by resource-type (instance, volume, image, snapshot, vpc, subnet, security-group, route-table, internet-gateway, egress-only-internet-gateway) 4. Filter by key 5. Filter by value 6. Empty result when no tags exist |
DONE |
EC2 - Regions & Availability Zones
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
describe-regions |
(returns configured region, endpoint, opt-in status from config — input params ignored) | --region-names, --filters, --all-regions, --dry-run |
None | Return configured region from spinifex init config with Endpoint and OptInStatus → local-only response, no NATS | 1. List all regions 2. Filter by region name 3. Verify endpoint URL returned 4. Verify OptInStatus returned |
DONE |
describe-availability-zones |
(returns configured AZ, region, zone ID, state, opt-in status from config — input params ignored) | --zone-names, --filters, --all-availability-zones |
None | Return configured AZ from spinifex init config with zone ID, state, group name, network border group → local-only response, no NATS | 1. List all AZs 2. Filter by zone name 3. Verify zone state is available 4. Verify region name matches config |
DONE |
EC2 - Account Attributes
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
describe-account-attributes |
--attribute-names |
--dry-run |
None | Gateway parses input → returns static account attributes: supported-platforms=VPC, default-vpc=none, max-instances=100, vpc-max-security-groups-per-interface=5, max-elastic-ips=5, vpc-max-elastic-ips=20 → local-only response, no NATS | 1. List all account attributes 2. Filter by attribute name 3. Verify all 6 attributes returned with correct values |
DONE |
EC2 - Account Settings (Deferred)
Persistence layer works (NATS JetStream KV), but stored values are not yet enforced by downstream services. Will be completed post-merge.
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
enable-ebs-encryption-by-default |
(no flags needed) | --dry-run |
None | Gateway validates input → NATS ec2.EnableEbsEncryptionByDefault with spinifex-workers queue group → daemon stores EbsEncryptionByDefault=true in JetStream KV (spinifex-ec2-account-settings bucket) → return EbsEncryptionByDefault=true. Enforcement deferred: stored value not yet checked by CreateVolume/CreateSnapshot. |
1. Enable encryption 2. Verify via get |
STARTED (enforcement pending) |
disable-ebs-encryption-by-default |
(no flags needed) | --dry-run |
None | Gateway validates input → NATS ec2.DisableEbsEncryptionByDefault with spinifex-workers queue group → daemon stores EbsEncryptionByDefault=false in JetStream KV → return EbsEncryptionByDefault=false. |
1. Disable encryption 2. Verify via get |
STARTED (enforcement pending) |
get-ebs-encryption-by-default |
(no flags needed) | --dry-run |
None | Gateway validates input → NATS ec2.GetEbsEncryptionByDefault with spinifex-workers queue group → daemon reads EbsEncryptionByDefault from JetStream KV → return current state. |
1. Get default state 2. Verify matches last enable/disable |
STARTED (enforcement pending) |
enable-serial-console-access |
(no flags needed) | --dry-run |
None | Gateway validates input → NATS ec2.EnableSerialConsoleAccess with spinifex-workers queue group → daemon stores SerialConsoleAccessEnabled=true in JetStream KV (spinifex-ec2-account-settings bucket) → return enabled=true. Stored for future interactive serial console support (not currently enforced by any operation). |
1. Enable access 2. Verify via get |
STARTED (enforcement pending) |
disable-serial-console-access |
(no flags needed) | --dry-run |
None | Gateway validates input → NATS ec2.DisableSerialConsoleAccess with spinifex-workers queue group → daemon stores SerialConsoleAccessEnabled=false in JetStream KV → return enabled=false. Stored for future interactive serial console support (not currently enforced by any operation). |
1. Disable access 2. Verify via get |
STARTED (enforcement pending) |
get-serial-console-access-status |
(no flags needed) | --dry-run |
None | Gateway validates input → NATS ec2.GetSerialConsoleAccessStatus with spinifex-workers queue group → daemon reads SerialConsoleAccessEnabled from JetStream KV → return current state. |
1. Get current status 2. Verify matches last enable/disable |
DONE |
enable-snapshot-block-public-access |
— | --state |
None | Store snapshot block public access state in JetStream KV | 1. Enable with block-all-sharing 2. Enable with block-new-sharing |
NOT STARTED (enforcement pending) |
disable-snapshot-block-public-access |
— | --dry-run |
None | Clear snapshot block public access state in JetStream KV | 1. Disable access | NOT STARTED (enforcement pending) |
get-snapshot-block-public-access-state |
— | --dry-run |
None | Read snapshot block public access state from JetStream KV | 1. Get current state | NOT STARTED (enforcement pending) |
enable-image-block-public-access |
— | --image-block-public-access-state |
None | Store image block public access state in JetStream KV | 1. Enable with block-new-sharing | NOT STARTED (enforcement pending) |
disable-image-block-public-access |
— | --dry-run |
None | Clear image block public access state in JetStream KV | 1. Disable access | NOT STARTED (enforcement pending) |
get-image-block-public-access-state |
— | --dry-run |
None | Read image block public access state from JetStream KV | 1. Get current state | NOT STARTED (enforcement pending) |
modify-instance-metadata-defaults |
— | --http-tokens, --http-put-response-hop-limit, --http-endpoint, --instance-metadata-tags |
None | Store instance metadata defaults in JetStream KV | 1. Set httpTokens=required 2. Set hop limit |
NOT STARTED (enforcement pending) |
get-instance-metadata-defaults |
— | --dry-run |
None | Read instance metadata defaults from JetStream KV | 1. Get current defaults | NOT STARTED (enforcement pending) |
EC2 - Egress-Only Internet Gateway
Gateway routing and KV CRUD are wired up but no OVN/OVS integration exists. EIGWs are stored in spinifex-eigw KV bucket but have no effect on network topology (no vpcd events published, unlike IGW which publishes vpc.igw-attach/vpc.igw-detach).
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-egress-only-internet-gateway |
--vpc-id, --tag-specifications |
--client-token, --dry-run |
VPC must exist | Gateway validates VpcId → NATS ec2.CreateEgressOnlyInternetGateway → daemon generates eigw-ID → creates EgressOnlyIGWRecord in spinifex-eigw KV bucket with state=attached (always attached at creation, unlike IGW) → returns EgressOnlyInternetGateway with attachment |
1. Create with valid VpcId 2. Missing VpcId (error: MissingParameter) 3. Create with inline tags 4. Verify ID format eigw-{16hex} |
STARTED (KV only, no OVN) |
delete-egress-only-internet-gateway |
--egress-only-internet-gateway-id |
--dry-run |
EIGW must exist | NATS ec2.DeleteEgressOnlyInternetGateway → daemon verifies EIGW exists in KV → deletes from spinifex-eigw KV → returns ReturnCode=true. No detach required (unlike IGW). |
1. Delete existing EIGW 2. Delete non-existent EIGW (error) 3. Missing ID (error: MissingParameter) |
STARTED (KV only, no OVN) |
describe-egress-only-internet-gateways |
--egress-only-internet-gateway-ids |
--filters, --max-results, --next-token, --dry-run |
None | NATS ec2.DescribeEgressOnlyInternetGateways → daemon lists all keys from spinifex-eigw KV bucket → filters by ID if specified → always includes Attachments array → returns list |
1. Describe all EIGWs 2. Filter by specific ID 3. Non-existent ID returns empty list |
STARTED (KV only, no OVN) |
EC2 - Placement Groups
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-placement-group |
— | --group-name, --strategy (cluster/spread/partition), --partition-count, --spread-level, --tag-specifications |
None | NATS ec2.CreatePlacementGroup → daemon stores placement group in JetStream KV with strategy, state=available → return group metadata |
1. Create with cluster strategy 2. Create with spread strategy 3. Create with partition strategy + partition count 4. Duplicate name (error) 5. Invalid strategy (error) |
NOT STARTED |
delete-placement-group |
— | --group-name, --dry-run |
Group must exist, no instances in group | NATS ec2.DeletePlacementGroup → daemon verifies group exists and is empty → delete from KV → return success |
1. Delete empty group 2. Delete group with instances (error) 3. Delete non-existent group (error) |
NOT STARTED |
describe-placement-groups |
— | --group-names, --group-ids, --filters |
None | NATS ec2.DescribePlacementGroups → daemon lists groups from KV → return list with strategy and state |
1. List all groups 2. Filter by name 3. Filter by strategy |
NOT STARTED |
EC2 - Dedicated Hosts
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
allocate-hosts |
— | --availability-zone, --instance-type, --quantity, --auto-placement, --tag-specifications |
None | NATS ec2.AllocateHosts → daemon allocates host resources → stores in JetStream KV with h-ID → return host ID list |
1. Allocate single host 2. Allocate multiple hosts 3. Missing AZ or instance type (error) |
NOT STARTED |
describe-hosts |
— | --host-ids, --filters, --max-results |
None | NATS ec2.DescribeHosts → daemon lists hosts from KV → return host list with capacity and instance info |
1. List all hosts 2. Filter by host ID 3. Filter by instance type |
NOT STARTED |
release-hosts |
— | --host-ids |
Host must exist, no running instances | NATS ec2.ReleaseHosts → daemon verifies no instances on host → delete from KV → return success/failure per host |
1. Release empty host 2. Release host with instances (error) 3. Release non-existent host (error) |
NOT STARTED |
EC2 - IPv4 Pools
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-public-ipv4-pool |
— | --tag-specifications, --dry-run |
None | NATS ec2.CreatePublicIpv4Pool → daemon creates pool in JetStream KV with ipv4pool-ID → return pool ID |
1. Create pool 2. Verify in describe |
NOT STARTED |
delete-public-ipv4-pool |
— | --pool-id, --dry-run |
Pool must exist and be empty | NATS ec2.DeletePublicIpv4Pool → daemon verifies pool empty → delete from KV → return success |
1. Delete empty pool 2. Delete pool with addresses (error) |
NOT STARTED |
describe-public-ipv4-pools |
— | --pool-ids, --filters, --max-results |
None | NATS ec2.DescribePublicIpv4Pools → daemon lists pools from KV → return pool list with address counts |
1. List all pools 2. Filter by pool ID |
NOT STARTED |
EC2 - DHCP Options
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-dhcp-options |
— | --dhcp-configurations (domain-name, domain-name-servers, ntp-servers, netbios-name-servers, netbios-node-type), --tag-specifications |
None | NATS ec2.CreateDhcpOptions → daemon stores DHCP option set in JetStream KV with dopt-ID → return DHCP options with configurations |
1. Create with domain-name + DNS servers 2. Create with NTP servers 3. Verify in describe |
NOT STARTED |
delete-dhcp-options |
— | --dhcp-options-id, --dry-run |
DHCP options must exist and not be associated with a VPC | NATS ec2.DeleteDhcpOptions → daemon verifies not associated → delete from KV → return success |
1. Delete unassociated options 2. Delete associated options (error) 3. Delete non-existent (error) |
NOT STARTED |
describe-dhcp-options |
— | --dhcp-options-ids, --filters, --max-results |
None | NATS ec2.DescribeDhcpOptions → daemon lists DHCP options from KV → return list with configurations |
1. List all DHCP options 2. Filter by ID |
NOT STARTED |
associate-dhcp-options |
— | --dhcp-options-id, --vpc-id, --dry-run |
DHCP options and VPC must exist | NATS ec2.AssociateDhcpOptions → daemon links DHCP options to VPC → update VPC metadata → return success |
1. Associate with VPC 2. Missing DHCP options ID (error) 3. Missing VPC ID (error) |
NOT STARTED |
EC2 - Capacity Reservations
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-capacity-reservation |
— | --instance-type, --instance-platform, --availability-zone, --instance-count, --end-date, --end-date-type, --instance-match-criteria, --tag-specifications |
None | NATS ec2.CreateCapacityReservation → daemon stores reservation in JetStream KV with cr-ID → return reservation with state=active |
1. Create with required fields 2. Missing instance type (error) 3. Verify in describe |
NOT STARTED |
cancel-capacity-reservation |
— | --capacity-reservation-id, --dry-run |
Reservation must exist | NATS ec2.CancelCapacityReservation → daemon marks reservation as cancelled in KV → return success |
1. Cancel active reservation 2. Cancel non-existent (error) |
NOT STARTED |
describe-capacity-reservations |
— | --capacity-reservation-ids, --filters, --max-results |
None | NATS ec2.DescribeCapacityReservations → daemon lists reservations from KV → return list with state and counts |
1. List all reservations 2. Filter by ID 3. Filter by instance type |
NOT STARTED |
modify-capacity-reservation |
— | --capacity-reservation-id, --instance-count, --end-date, --end-date-type |
Reservation must exist | NATS ec2.ModifyCapacityReservation → daemon updates reservation in KV → return success |
1. Modify instance count 2. Modify end date 3. Missing reservation ID (error) |
NOT STARTED |
EC2 - Elastic IP
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
allocate-address |
— | --domain (vpc), --tag-specifications, --dry-run |
None | NATS ec2.AllocateAddress → daemon allocates IP from pool → stores in JetStream KV with eipalloc-ID → return AllocationId, PublicIp, Domain |
1. Allocate EIP 2. Verify in describe-addresses 3. Allocate without --domain uses default |
NOT STARTED |
release-address |
— | --allocation-id, --dry-run |
EIP must exist and be disassociated | NATS ec2.ReleaseAddress → daemon verifies not associated → release IP back to pool → delete from KV → return success |
1. Release disassociated EIP 2. Release associated EIP (error) 3. Release non-existent (error) |
NOT STARTED |
associate-address |
— | --allocation-id, --instance-id or --network-interface-id, --dry-run |
EIP and target must exist | NATS ec2.AssociateAddress → daemon links EIP to instance/ENI → configure OVS DNAT/SNAT rules → return eipassoc-ID |
1. Associate with instance 2. Re-associate (moves EIP) 3. Associate non-existent EIP (error) |
NOT STARTED |
disassociate-address |
— | --association-id, --dry-run |
Association must exist | NATS ec2.DisassociateAddress → daemon unlinks EIP from instance/ENI → remove OVS DNAT/SNAT rules → return success |
1. Disassociate existing 2. Disassociate non-existent (error) |
NOT STARTED |
describe-addresses |
— | --allocation-ids, --filters, --public-ips |
None | NATS ec2.DescribeAddresses → daemon lists EIPs from KV → return list with association info |
1. List all EIPs 2. Filter by allocation ID 3. Filter by public IP |
NOT STARTED |
EC2 - NAT Gateway
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-nat-gateway |
— | --subnet-id, --allocation-id, --connectivity-type, --tag-specifications |
Subnet must exist, EIP must be allocated (for public type) | NATS ec2.CreateNatGateway → daemon creates NAT gateway in JetStream KV with nat-ID → configure OVS SNAT rules → state=pending→available → return NatGateway |
1. Create public NAT gateway 2. Create private NAT gateway (no allocation-id) 3. Invalid subnet (error) |
NOT STARTED |
delete-nat-gateway |
— | --nat-gateway-id, --dry-run |
NAT gateway must exist | NATS ec2.DeleteNatGateway → daemon marks as deleting → remove OVS SNAT rules → delete from KV → return NatGatewayId |
1. Delete existing NAT gateway 2. Delete non-existent (error) |
NOT STARTED |
describe-nat-gateways |
— | --nat-gateway-ids, --filters, --max-results |
None | NATS ec2.DescribeNatGateways → daemon lists NAT gateways from KV → return list with state, subnet, addresses |
1. List all NAT gateways 2. Filter by ID 3. Filter by VPC ID |
NOT STARTED |
assign-private-nat-gateway-address |
— | --nat-gateway-id, --private-ip-addresses |
NAT gateway must exist (private type) | NATS ec2.AssignPrivateNatGatewayAddress → daemon assigns private IPs to NAT gateway → return assigned addresses |
1. Assign address 2. Missing NAT gateway ID (error) |
NOT STARTED |
associate-nat-gateway-address |
— | --nat-gateway-id, --allocation-ids |
NAT gateway and EIPs must exist | NATS ec2.AssociateNatGatewayAddress → daemon associates EIPs with NAT gateway → return association info |
1. Associate EIP 2. Missing NAT gateway ID (error) |
NOT STARTED |
EC2 - VPC Networking (Core)
Requires OVN/OVS (apt install openvswitch-switch ovn-host). VPC/Subnet/ENI CRUD stores metadata in NATS KV and publishes events to vpcd (the network topology daemon) which translates to OVN logical switches, ports, and routers. Single AZ for Spinifex v1. IPAM uses CAS-based allocation from NATS KV (spinifex-vpc-ipam bucket).
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-vpc |
--cidr-block, --tag-specifications |
--instance-tenancy, --dry-run |
None | Gateway validates CidrBlock (required, valid CIDR, /16–/28 prefix) → NATS ec2.CreateVpc → daemon allocates unique VNI from spinifex-vpc-vni-counter KV → generates vpc-ID → stores VPCRecord in spinifex-vpc-vpcs KV → publishes vpc.create event to vpcd (creates OVN logical switch) → returns VPC with state=available |
1. Create VPC with /16 CIDR 2. Invalid CIDR format (error: InvalidVpcRange) 3. CIDR prefix outside /16–/28 (error) 4. Verify in describe-vpcs 5. Tags applied at creation |
DONE |
delete-vpc |
--vpc-id |
--dry-run |
VPC must be empty (no subnets) | Gateway validates VpcId → NATS ec2.DeleteVpc → daemon checks for dependent subnets in spinifex-vpc-subnets KV → returns DependencyViolation if subnets exist → deletes from spinifex-vpc-vpcs KV → publishes vpc.delete event to vpcd (removes OVN resources) |
1. Delete empty VPC 2. Delete VPC with subnets (DependencyViolation) 3. Delete non-existent VPC (error) |
DONE |
describe-vpcs |
--vpc-ids |
--filters, --max-results, --dry-run |
None | NATS ec2.DescribeVpcs → daemon lists all keys from spinifex-vpc-vpcs KV → filters by VPC IDs if specified → returns error for non-existent requested IDs → returns VPC list with CIDR, state, CIDR associations |
1. List all VPCs 2. Filter by VPC ID 3. Non-existent VPC ID returns error |
DONE |
modify-vpc-attribute |
— | --vpc-id, --enable-dns-support, --enable-dns-hostnames |
VPC must exist | NATS ec2.ModifyVpcAttribute → daemon updates VPC attribute in KV → return success |
1. Enable DNS support 2. Enable DNS hostnames 3. Missing VPC ID (error) |
NOT STARTED |
associate-vpc-cidr-block |
— | --vpc-id, --cidr-block |
VPC must exist | NATS ec2.AssociateVpcCidrBlock → daemon adds secondary CIDR to VPC metadata → return association |
1. Add secondary CIDR 2. Overlapping CIDR (error) 3. Max CIDR blocks exceeded (error) |
NOT STARTED |
disassociate-vpc-cidr-block |
— | --association-id |
Association must exist | NATS ec2.DisassociateVpcCidrBlock → daemon removes secondary CIDR from VPC → return success |
1. Remove secondary CIDR 2. Remove primary CIDR (error) |
NOT STARTED |
create-subnet |
--vpc-id, --cidr-block, --availability-zone, --tag-specifications |
--dry-run |
VPC must exist | Gateway validates VpcId + CidrBlock (required, valid CIDR, /16–/28 prefix) → NATS ec2.CreateSubnet → daemon verifies parent VPC exists → validates subnet CIDR is within VPC CIDR range → checks for overlapping subnets in same VPC → calculates available IPs (total hosts - 5 AWS-reserved: .0/.1/.2/.3/.255) → generates subnet-ID → stores SubnetRecord in spinifex-vpc-subnets KV → publishes vpc.create-subnet event to vpcd → defaults AZ from config if omitted |
1. Create subnet within VPC CIDR 2. Subnet CIDR outside VPC range (error: InvalidSubnetRange) 3. Overlapping subnet CIDRs (error: InvalidSubnetConflict) 4. CIDR prefix outside /16–/28 (error) 5. Missing VPC or CIDR (error: MissingParameter) |
DONE |
delete-subnet |
--subnet-id |
--dry-run |
Subnet must exist | NATS ec2.DeleteSubnet → daemon verifies subnet exists → deletes from spinifex-vpc-subnets KV → publishes vpc.delete-subnet event to vpcd |
1. Delete existing subnet 2. Delete non-existent subnet (error) |
DONE |
describe-subnets |
--subnet-ids, --filters (vpc-id) |
--max-results, --dry-run |
None | NATS ec2.DescribeSubnets → daemon lists all keys from spinifex-vpc-subnets KV → applies vpc-id filter if present → filters by subnet IDs if specified → recalculates available IPs from CIDR → returns error for non-existent requested IDs |
1. List all subnets 2. Filter by VPC ID 3. Filter by subnet ID 4. Non-existent subnet returns error |
DONE |
modify-subnet-attribute |
— | --subnet-id, --map-public-ip-on-launch, --assign-ipv6-address-on-creation |
Subnet must exist | NATS ec2.ModifySubnetAttribute → daemon updates subnet attribute in KV → return success |
1. Enable auto-assign public IP 2. Missing subnet ID (error) |
NOT STARTED |
associate-subnet-cidr-block |
— | --subnet-id, --ipv6-cidr-block |
Subnet must exist | NATS ec2.AssociateSubnetCidrBlock → daemon adds IPv6 CIDR to subnet → return association |
1. Add IPv6 CIDR 2. Missing subnet ID (error) |
NOT STARTED |
disassociate-subnet-cidr-block |
— | --association-id |
Association must exist | NATS ec2.DisassociateSubnetCidrBlock → daemon removes IPv6 CIDR from subnet → return success |
1. Remove IPv6 CIDR 2. Invalid association (error) |
NOT STARTED |
create-security-group |
— | --group-name, --description, --vpc-id, --tag-specifications |
VPC must exist | Create security group metadata → create default OVS flow rules (deny all inbound, allow all outbound) → assign sg-ID | 1. Create SG in VPC 2. Duplicate name in same VPC (error) 3. Verify default rules |
NOT STARTED |
delete-security-group |
— | --group-id |
SG must not be in use by instances | Verify no instances reference SG → remove OVS flow rules → delete metadata | 1. Delete unused SG 2. Delete SG in use (error) |
NOT STARTED |
describe-security-groups |
— | --group-ids, --group-names, --filters, --max-results |
None | Query NATS KV for SG metadata → return SG list with rules | 1. List all SGs 2. Filter by VPC ID 3. Filter by group name |
NOT STARTED |
authorize-security-group-ingress |
— | --group-id, --protocol, --port, --cidr, --source-group |
SG must exist | Add inbound rule → create OVS OpenFlow rule → persist to metadata | 1. Allow SSH (port 22) from 0.0.0.0/0 2. Allow from specific CIDR 3. Duplicate rule (idempotent) |
NOT STARTED |
authorize-security-group-egress |
— | --group-id, --protocol, --port, --cidr |
SG must exist | Add outbound rule → create OVS OpenFlow rule → persist to metadata | 1. Allow HTTPS outbound 2. Restrict to specific CIDR |
NOT STARTED |
revoke-security-group-ingress |
— | --group-id, --protocol, --port, --cidr |
SG must exist, rule must exist | Remove inbound rule → delete OVS OpenFlow rule → update metadata | 1. Revoke existing rule 2. Revoke non-existent rule (error) |
NOT STARTED |
revoke-security-group-egress |
— | --group-id, --protocol, --port, --cidr |
SG must exist, rule must exist | Remove outbound rule → delete OVS OpenFlow rule → update metadata | 1. Revoke existing rule 2. Revoke non-existent rule (error) |
NOT STARTED |
create-internet-gateway |
--tag-specifications |
--dry-run |
None | NATS ec2.CreateInternetGateway → daemon generates igw-ID → creates IGWRecord in spinifex-igw KV bucket with state=available (not attached) → returns InternetGateway with empty Attachments |
1. Create IGW 2. Verify in describe-internet-gateways 3. Tags applied at creation |
DONE |
attach-internet-gateway |
--internet-gateway-id, --vpc-id |
--dry-run |
IGW and VPC must exist | NATS ec2.AttachInternetGateway → daemon validates IGW exists and is not already attached (ResourceAlreadyAssociated) → sets VpcId on record → state=attached → publishes vpc.igw-attach event to vpcd (creates OVN external switch, gateway port, SNAT rules) |
1. Attach IGW to VPC 2. Attach already-attached IGW (ResourceAlreadyAssociated) 3. Non-existent IGW (error) 4. Missing params (MissingParameter) |
DONE |
detach-internet-gateway |
--internet-gateway-id, --vpc-id |
--dry-run |
IGW must be attached to specified VPC | NATS ec2.DetachInternetGateway → daemon validates IGW exists and is attached to specified VPC (GatewayNotAttached if wrong VPC) → clears VpcId → state=available → publishes vpc.igw-detach event to vpcd (removes OVN resources) |
1. Detach attached IGW 2. Detach from wrong VPC (GatewayNotAttached) 3. Detach unattached IGW (error) |
DONE |
delete-internet-gateway |
--internet-gateway-id |
--dry-run |
IGW must be detached | NATS ec2.DeleteInternetGateway → daemon validates IGW exists and VpcId is empty (DependencyViolation if attached) → deletes from spinifex-igw KV |
1. Delete detached IGW 2. Delete attached IGW (DependencyViolation) 3. Delete non-existent (error) |
DONE |
describe-internet-gateways |
--internet-gateway-ids |
--filters, --max-results, --dry-run |
None | NATS ec2.DescribeInternetGateways → daemon lists all keys from spinifex-igw KV → filters by IGW IDs if specified → returns list with Attachments array (if attached) |
1. List all IGWs 2. Filter by IGW ID 3. Verify attachment info |
DONE |
EC2 - Route Tables
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-route-table |
— | --vpc-id, --tag-specifications |
VPC must exist | NATS ec2.CreateRouteTable → daemon creates route table in KV with rtb-ID → add default local route for VPC CIDR → return route table |
1. Create route table 2. Verify default local route 3. Missing VPC ID (error) |
NOT STARTED |
delete-route-table |
— | --route-table-id, --dry-run |
Route table must exist and not be main route table | NATS ec2.DeleteRouteTable → daemon verifies not main table → delete from KV → return success |
1. Delete non-main route table 2. Delete main route table (error) 3. Delete non-existent (error) |
NOT STARTED |
describe-route-tables |
— | --route-table-ids, --filters, --max-results |
None | NATS ec2.DescribeRouteTables → daemon lists route tables from KV → return tables with routes |
1. List all route tables 2. Filter by VPC |
NOT STARTED |
create-route |
— | --route-table-id, --destination-cidr-block, --gateway-id or --nat-gateway-id or --egress-only-internet-gateway-id |
Route table and target must exist | NATS ec2.CreateRoute → daemon adds route entry → configure OVS flow for destination CIDR → update metadata |
1. Add internet route via IGW 2. Add route to NAT gateway 3. Add route to EIGW 4. Conflicting route (error) |
NOT STARTED |
delete-route |
— | --route-table-id, --destination-cidr-block |
Route must exist | NATS ec2.DeleteRoute → daemon removes route entry → remove OVS flow → update metadata |
1. Delete existing route 2. Delete non-existent route (error) 3. Delete local route (error) |
NOT STARTED |
replace-route |
— | --route-table-id, --destination-cidr-block, --gateway-id or --nat-gateway-id |
Route must exist | NATS ec2.ReplaceRoute → daemon replaces route target → update OVS flow → update metadata |
1. Replace IGW route with NAT 2. Replace non-existent route (error) |
NOT STARTED |
associate-route-table |
— | --route-table-id, --subnet-id or --gateway-id |
Route table and target must exist | NATS ec2.AssociateRouteTable → daemon links route table to subnet/gateway → return rtbassoc-ID |
1. Associate with subnet 2. Missing route table ID (error) |
NOT STARTED |
disassociate-route-table |
— | --association-id |
Association must exist, not main table association | NATS ec2.DisassociateRouteTable → daemon unlinks route table → return success |
1. Disassociate existing 2. Disassociate main table (error) |
NOT STARTED |
EC2 - VPC Peering
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-vpc-peering-connection |
— | --vpc-id, --peer-vpc-id, --peer-owner-id, --peer-region, --tag-specifications |
Both VPCs must exist | NATS ec2.CreateVpcPeeringConnection → daemon creates peering in KV with pcx-ID → state=pending-acceptance → return peering connection |
1. Create peering between two VPCs 2. Missing VPC ID (error) 3. Overlapping CIDRs (error) |
NOT STARTED |
accept-vpc-peering-connection |
— | --vpc-peering-connection-id, --dry-run |
Peering must exist in pending-acceptance state | NATS ec2.AcceptVpcPeeringConnection → daemon updates state to active → configure OVS cross-VPC flows → return peering |
1. Accept pending peering 2. Accept already active (error) |
NOT STARTED |
reject-vpc-peering-connection |
— | --vpc-peering-connection-id, --dry-run |
Peering must exist in pending-acceptance state | NATS ec2.RejectVpcPeeringConnection → daemon updates state to rejected → return peering |
1. Reject pending peering 2. Reject already active (error) |
NOT STARTED |
delete-vpc-peering-connection |
— | --vpc-peering-connection-id, --dry-run |
Peering must exist | NATS ec2.DeleteVpcPeeringConnection → daemon removes OVS cross-VPC flows → delete from KV → return success |
1. Delete active peering 2. Delete non-existent (error) |
NOT STARTED |
describe-vpc-peering-connections |
— | --vpc-peering-connection-ids, --filters, --max-results |
None | NATS ec2.DescribeVpcPeeringConnections → daemon lists peerings from KV → return list with state |
1. List all peerings 2. Filter by ID 3. Filter by VPC |
NOT STARTED |
modify-vpc-peering-connection-options |
— | --vpc-peering-connection-id, --requester-peering-connection-options, --accepter-peering-connection-options |
Peering must be active | NATS ec2.ModifyVpcPeeringConnectionOptions → daemon updates peering options in KV → return modified options |
1. Enable DNS resolution 2. Missing peering ID (error) |
NOT STARTED |
EC2 - VPC Endpoints
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-vpc-endpoint |
— | --vpc-id, --service-name, --vpc-endpoint-type (Gateway/Interface), --route-table-ids, --subnet-ids, --tag-specifications |
VPC must exist | NATS ec2.CreateVpcEndpoint → daemon creates endpoint in KV with vpce-ID → return endpoint |
1. Create Gateway endpoint for S3 2. Create Interface endpoint 3. Missing VPC ID (error) |
NOT STARTED |
delete-vpc-endpoints |
— | --vpc-endpoint-ids, --dry-run |
Endpoints must exist | NATS ec2.DeleteVpcEndpoints → daemon deletes endpoints from KV → return success/failure per endpoint |
1. Delete existing endpoint 2. Delete non-existent (error) |
NOT STARTED |
describe-vpc-endpoints |
— | --vpc-endpoint-ids, --filters, --max-results |
None | NATS ec2.DescribeVpcEndpoints → daemon lists endpoints from KV → return list with state and type |
1. List all endpoints 2. Filter by VPC 3. Filter by service name |
NOT STARTED |
describe-vpc-endpoint-services |
— | --service-names, --filters, --max-results |
None | NATS ec2.DescribeVpcEndpointServices → daemon returns available services (S3, DynamoDB equivalents) → return service list |
1. List available services 2. Filter by service name |
NOT STARTED |
modify-vpc-endpoint |
— | --vpc-endpoint-id, --add-route-table-ids, --remove-route-table-ids, --add-subnet-ids, --remove-subnet-ids |
Endpoint must exist | NATS ec2.ModifyVpcEndpoint → daemon updates endpoint route tables/subnets in KV → return success |
1. Add route table 2. Remove subnet 3. Missing endpoint ID (error) |
NOT STARTED |
EC2 - VPN Gateway & Customer Gateway
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-customer-gateway |
— | --type (ipsec.1), --bgp-asn, --ip-address, --tag-specifications |
None | NATS ec2.CreateCustomerGateway → daemon stores in JetStream KV with cgw-ID → return customer gateway |
1. Create with BGP ASN and IP 2. Missing required fields (error) |
NOT STARTED |
delete-customer-gateway |
— | --customer-gateway-id, --dry-run |
Must exist, no VPN connections | NATS ec2.DeleteCustomerGateway → daemon verifies no active connections → delete from KV → return success |
1. Delete existing 2. Delete with active VPN (error) |
NOT STARTED |
describe-customer-gateways |
— | --customer-gateway-ids, --filters |
None | NATS ec2.DescribeCustomerGateways → daemon lists from KV → return list with BGP/IP info |
1. List all 2. Filter by ID |
NOT STARTED |
create-vpn-gateway |
— | --type (ipsec.1), --amazon-side-asn, --tag-specifications |
None | NATS ec2.CreateVpnGateway → daemon stores in JetStream KV with vgw-ID → return VPN gateway |
1. Create VPN gateway 2. Verify in describe |
NOT STARTED |
delete-vpn-gateway |
— | --vpn-gateway-id, --dry-run |
Must be detached | NATS ec2.DeleteVpnGateway → daemon verifies detached → delete from KV → return success |
1. Delete detached gateway 2. Delete attached (error) |
NOT STARTED |
attach-vpn-gateway |
— | --vpn-gateway-id, --vpc-id |
Both must exist | NATS ec2.AttachVpnGateway → daemon links VPN gateway to VPC → return attachment |
1. Attach to VPC 2. Already attached (error) |
NOT STARTED |
detach-vpn-gateway |
— | --vpn-gateway-id, --vpc-id |
Must be attached | NATS ec2.DetachVpnGateway → daemon unlinks VPN gateway from VPC → return success |
1. Detach from VPC 2. Not attached (error) |
NOT STARTED |
describe-vpn-gateways |
— | --vpn-gateway-ids, --filters |
None | NATS ec2.DescribeVpnGateways → daemon lists from KV → return list with attachment info |
1. List all 2. Filter by VPC attachment |
NOT STARTED |
EC2 - VPN Connections
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-vpn-connection |
— | --type (ipsec.1), --customer-gateway-id, --vpn-gateway-id, --options, --tag-specifications |
Customer gateway and VPN gateway must exist | NATS ec2.CreateVpnConnection → daemon stores connection in JetStream KV with vpn-ID → return connection with tunnel info |
1. Create IPsec connection 2. Missing customer gateway (error) 3. Missing VPN gateway (error) |
NOT STARTED |
delete-vpn-connection |
— | --vpn-connection-id, --dry-run |
Connection must exist | NATS ec2.DeleteVpnConnection → daemon deletes from KV → return success |
1. Delete existing connection 2. Delete non-existent (error) |
NOT STARTED |
describe-vpn-connections |
— | --vpn-connection-ids, --filters |
None | NATS ec2.DescribeVpnConnections → daemon lists from KV → return list with state and tunnel info |
1. List all connections 2. Filter by ID 3. Filter by VPN gateway |
NOT STARTED |
modify-vpn-connection |
— | --vpn-connection-id, --vpn-gateway-id, --customer-gateway-id |
Connection must exist | NATS ec2.ModifyVpnConnection → daemon updates connection target in KV → return modified connection |
1. Change VPN gateway 2. Missing connection ID (error) |
NOT STARTED |
modify-vpn-connection-options |
— | --vpn-connection-id, --local-ipv4-network-cidr, --remote-ipv4-network-cidr |
Connection must exist | NATS ec2.ModifyVpnConnectionOptions → daemon updates tunnel options in KV → return modified connection |
1. Modify CIDR ranges 2. Missing connection ID (error) |
NOT STARTED |
EC2 - Network ACLs
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-network-acl |
— | --vpc-id, --tag-specifications |
VPC must exist | NATS ec2.CreateNetworkAcl → daemon creates NACL in KV with acl-ID → add default deny-all rules → return NACL |
1. Create NACL in VPC 2. Missing VPC ID (error) 3. Verify default deny rules |
NOT STARTED |
delete-network-acl |
— | --network-acl-id, --dry-run |
Must not be default NACL | NATS ec2.DeleteNetworkAcl → daemon verifies not default → delete from KV → return success |
1. Delete non-default NACL 2. Delete default NACL (error) |
NOT STARTED |
describe-network-acls |
— | --network-acl-ids, --filters, --max-results |
None | NATS ec2.DescribeNetworkAcls → daemon lists NACLs from KV → return list with entries |
1. List all NACLs 2. Filter by VPC |
NOT STARTED |
create-network-acl-entry |
— | --network-acl-id, --rule-number, --protocol, --rule-action (allow/deny), --cidr-block, --ingress/--egress, --port-range |
NACL must exist | NATS ec2.CreateNetworkAclEntry → daemon adds rule entry to NACL in KV → configure OVS flow → return success |
1. Add ingress allow rule 2. Add egress deny rule 3. ICMP type/code parsing 4. Missing NACL ID (error) |
NOT STARTED |
delete-network-acl-entry |
— | --network-acl-id, --rule-number, --ingress/--egress |
Entry must exist | NATS ec2.DeleteNetworkAclEntry → daemon removes rule from NACL → remove OVS flow → return success |
1. Delete existing entry 2. Delete non-existent entry (error) |
NOT STARTED |
replace-network-acl-association |
— | --association-id, --network-acl-id |
Both must exist | NATS ec2.ReplaceNetworkAclAssociation → daemon swaps NACL for subnet → return new association ID |
1. Replace association 2. Missing association ID (error) |
NOT STARTED |
replace-network-acl-entry |
— | --network-acl-id, --rule-number, --protocol, --rule-action, --cidr-block, --ingress/--egress, --port-range |
Entry must exist | NATS ec2.ReplaceNetworkAclEntry → daemon replaces rule in KV → update OVS flow → return success |
1. Replace existing entry 2. Replace non-existent entry (error) |
NOT STARTED |
EC2 - Prefix Lists
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-managed-prefix-list |
— | --prefix-list-name, --address-family (IPv4/IPv6), --max-entries, --entries, --tag-specifications |
None | NATS ec2.CreateManagedPrefixList → daemon stores prefix list in JetStream KV with pl-ID → return prefix list with version |
1. Create with IPv4 entries 2. Name, address family, max entries required 3. Verify in describe |
NOT STARTED |
delete-managed-prefix-list |
— | --prefix-list-id, --dry-run |
Must exist, not referenced by security groups/route tables | NATS ec2.DeleteManagedPrefixList → daemon verifies not referenced → delete from KV → return success |
1. Delete unreferenced list 2. Delete referenced list (error) |
NOT STARTED |
describe-managed-prefix-lists |
— | --prefix-list-ids, --filters, --max-results |
None | NATS ec2.DescribeManagedPrefixLists → daemon lists from KV → return list with state and version |
1. List all prefix lists 2. Filter by ID |
NOT STARTED |
modify-managed-prefix-list |
— | --prefix-list-id, --current-version, --add-entries, --remove-entries, --prefix-list-name |
Must exist, current version must match | NATS ec2.ModifyManagedPrefixList → daemon validates version → apply changes → increment version → return updated list |
1. Add entries 2. Remove entries 3. Version mismatch (error) |
NOT STARTED |
get-managed-prefix-list-entries |
— | --prefix-list-id, --target-version, --max-results |
Prefix list must exist | NATS ec2.GetManagedPrefixListEntries → daemon reads entries from KV → return entry list |
1. List entries 2. List entries at specific version |
NOT STARTED |
get-managed-prefix-list-associations |
— | --prefix-list-id, --max-results |
Prefix list must exist | NATS ec2.GetManagedPrefixListAssociations → daemon finds references in SGs/route tables → return association list |
1. List associations 2. No associations returns empty |
NOT STARTED |
EC2 - Network Interfaces
ENIs are managed via spinifex-vpc-enis KV bucket with CAS-based IPAM allocation from spinifex-vpc-ipam. ENIs are auto-created by run-instances --subnet-id and auto-deleted on termination. MAC addresses are deterministically generated from the ENI ID. Attach/detach are internal operations used by instance lifecycle — no standalone attach/detach API yet.
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-network-interface |
--subnet-id, --private-ip-address, --description, --tag-specifications |
--groups, --dry-run |
Subnet must exist | Gateway validates SubnetId (required) → NATS ec2.CreateNetworkInterface → daemon verifies subnet exists in spinifex-vpc-subnets KV → if no PrivateIpAddress, allocates from IPAM pool (CAS-based, up to 5 retries) → generates eni-ID → generates deterministic MAC from ENI ID → stores ENIRecord in spinifex-vpc-enis KV → publishes vpc.create-port event to vpcd (creates OVN logical port) → returns NetworkInterface with status=available |
1. Create in subnet (auto-allocate IP) 2. Create with specific private IP 3. Missing subnet ID (MissingParameter) 4. Non-existent subnet (error) 5. Tags applied at creation |
DONE |
delete-network-interface |
--network-interface-id |
--dry-run |
Must not be attached (status != in-use) | NATS ec2.DeleteNetworkInterface → daemon verifies ENI exists and status is not "in-use" (InvalidNetworkInterfaceInUse) → releases IP back to IPAM pool → deletes from spinifex-vpc-enis KV → publishes vpc.delete-port event to vpcd |
1. Delete detached ENI 2. Delete attached ENI (InvalidNetworkInterfaceInUse) 3. Delete non-existent (error) |
DONE |
describe-network-interfaces |
--network-interface-ids, --filters (subnet-id, vpc-id, attachment.instance-id) |
--max-results, --dry-run |
None | NATS ec2.DescribeNetworkInterfaces → daemon lists all keys from spinifex-vpc-enis KV → applies filters (subnet-id, vpc-id, attachment.instance-id) → filters by ENI IDs if specified → returns error for non-existent requested IDs |
1. List all ENIs 2. Filter by subnet-id 3. Filter by vpc-id 4. Filter by attachment.instance-id 5. Non-existent ENI returns error |
DONE |
attach-network-interface |
— | --network-interface-id, --instance-id, --device-index |
ENI and instance must exist | Internal operation used by run-instances --subnet-id. Marks ENI as "in-use", sets instance attachment, generates attachment ID. No standalone API endpoint yet. |
1. Attach ENI to instance 2. Already attached (error) 3. Missing device index (error) |
NOT STARTED (internal only) |
detach-network-interface |
— | --attachment-id, --force |
ENI must be attached | Internal operation used by terminate-instances. Marks ENI as "available", clears attachment info. No standalone API endpoint yet. | 1. Detach ENI 2. Force detach 3. Not attached (error) |
NOT STARTED (internal only) |
modify-network-interface-attribute |
— | --network-interface-id, --description, --source-dest-check, --groups |
ENI must exist | NATS ec2.ModifyNetworkInterfaceAttribute → daemon updates ENI attributes in KV → return success |
1. Modify description 2. Toggle source/dest check 3. Update security groups |
NOT STARTED |
assign-private-ip-addresses |
— | --network-interface-id, --private-ip-addresses or --secondary-private-ip-address-count |
ENI must exist | NATS ec2.AssignPrivateIpAddresses → daemon allocates IPs from subnet pool → assign to ENI → return assigned IPs |
1. Assign specific IPs 2. Assign by count 3. Cannot specify both (error) |
NOT STARTED |
unassign-private-ip-addresses |
— | --network-interface-id, --private-ip-addresses |
IPs must be assigned to ENI | NATS ec2.UnassignPrivateIpAddresses → daemon releases IPs → return success |
1. Unassign secondary IP 2. Unassign primary IP (error) |
NOT STARTED |
EC2 - Launch Templates
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-launch-template |
— | --launch-template-name, --launch-template-data (ImageId, InstanceType, etc.), --tag-specifications |
None | NATS ec2.CreateLaunchTemplate → daemon stores template in JetStream KV with lt-ID → version 1 → return template metadata |
1. Create template with full config 2. Duplicate name (error) 3. Verify in describe output |
NOT STARTED |
create-launch-template-version |
— | --launch-template-id or --launch-template-name, --launch-template-data, --source-version |
Template must exist | NATS ec2.CreateLaunchTemplateVersion → daemon creates new version in KV → return version details |
1. Create version from scratch 2. Create version from source version 3. Template not found (error) |
NOT STARTED |
delete-launch-template |
— | --launch-template-id or --launch-template-name, --dry-run |
Template must exist | NATS ec2.DeleteLaunchTemplate → daemon deletes template and all versions from KV → return deleted template info |
1. Delete by ID 2. Delete by name 3. Non-existent template (error) |
NOT STARTED |
describe-launch-templates |
— | --launch-template-ids, --launch-template-names, --filters |
None | NATS ec2.DescribeLaunchTemplates → daemon lists templates from KV → return list |
1. List all templates 2. Filter by name 3. Filter by ID |
NOT STARTED |
describe-launch-template-versions |
— | --launch-template-id or --launch-template-name, --versions, --min-version, --max-version |
Template must exist | NATS ec2.DescribeLaunchTemplateVersions → daemon lists versions from KV → return version list with data |
1. List all versions 2. List specific version 3. Template not found (error) |
NOT STARTED |
EC2 - Misc Operations
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
delete-network-interface-permission |
— | --network-interface-permission-id, --force |
Permission must exist | NATS ec2.DeleteNetworkInterfacePermission → daemon deletes permission from KV → return success |
1. Delete existing permission 2. Force delete |
NOT STARTED |
enable-address-transfer |
— | --allocation-id, --transfer-account-id |
EIP must exist | NATS ec2.EnableAddressTransfer → daemon stores transfer in JetStream KV with 7-day expiration → return AllocationId and TransferAccountId |
1. Enable transfer 2. Verify transfer stored with expiration |
NOT STARTED |
disable-address-transfer |
— | --allocation-id |
Transfer must exist | NATS ec2.DisableAddressTransfer → daemon updates transfer status to disabled in KV → return status |
1. Disable active transfer 2. Disable non-existent transfer (error) |
NOT STARTED |
EBS Direct API
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
start-snapshot |
— | --volume-size, --parent-snapshot-id, --description, --encrypted |
None | Initialize snapshot in viperblock → return snap-ID for block-level writes | 1. Start new snapshot 2. Start incremental from parent |
NOT STARTED |
put-snapshot-block |
— | --snapshot-id, --block-index, --block-data, --checksum |
start-snapshot | Write block data to snapshot at specified index in viperblock → verify checksum | 1. Write single block 2. Write with bad checksum (error) |
NOT STARTED |
get-snapshot-block |
— | --snapshot-id, --block-index |
Snapshot must exist | Read block data from snapshot at specified index → return data with checksum | 1. Read existing block 2. Read sparse block (zeros) |
NOT STARTED |
complete-snapshot |
— | --snapshot-id, --changed-blocks-count |
start-snapshot, put-snapshot-block | Finalize snapshot → mark as completed → sync to Predastore | 1. Complete valid snapshot 2. Complete with wrong block count (error) |
NOT STARTED |
list-snapshot-blocks |
— | --snapshot-id, --max-results, --next-token |
Snapshot must exist | List all non-empty blocks in snapshot with tokens and sizes | 1. List blocks of populated snapshot 2. List blocks of empty snapshot |
NOT STARTED |
list-changed-blocks |
— | --second-snapshot-id, --first-snapshot-id, --max-results |
Snapshots must exist | Compare two snapshots → return list of differing block indexes | 1. Compare parent and child snapshots 2. Compare unrelated snapshots |
NOT STARTED |
S3 (via Predastore)
Currently S3 operations are handled directly by Predastore. Consider moving control/data plane to Spinifex format for AWS gateway integration.
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
s3 mb (CreateBucket) |
— | s3://bucket-name |
None | NATS s3.createbucket → Predastore creates bucket → return success |
1. Create valid bucket 2. Duplicate bucket name (error) 3. Invalid bucket name (error) |
NOT STARTED (Predastore handles directly) |
s3 rb (DeleteBucket) |
— | s3://bucket-name, --force |
Bucket must exist | NATS s3.deletebucket → verify bucket empty (or force) → Predastore deletes bucket |
1. Delete empty bucket 2. Delete non-empty bucket (error) 3. Force delete non-empty bucket |
NOT STARTED |
s3 ls (ListBuckets/Objects) |
— | s3://bucket/prefix, --recursive |
None | NATS s3.listobjects → Predastore lists objects with prefix → return list |
1. List all buckets 2. List objects in bucket 3. List with prefix filter |
NOT STARTED |
s3 cp (PutObject/GetObject) |
— | source dest, --recursive, --acl |
Bucket must exist | NATS s3.putobject/s3.getobject → Predastore stores/retrieves with Reed-Solomon encoding |
1. Upload single file 2. Download single file 3. Recursive upload/download 4. Large file (multipart) |
NOT STARTED |
s3 rm (DeleteObject) |
— | s3://bucket/key, --recursive |
Object must exist | NATS s3.deleteobject → Predastore deletes object shards |
1. Delete single object 2. Recursive delete 3. Delete non-existent (idempotent) |
NOT STARTED |
s3 sync |
— | source dest, --delete, --exclude, --include |
Bucket must exist | Compare source and dest → upload changed/new files → optionally delete removed files | 1. Sync local dir to S3 2. Sync with --delete 3. Sync with exclude filter |
NOT STARTED |
IAM - User Management
All IAM operations are account-scoped. Users, policies, and access keys are namespaced per account. The gateway extracts the account ID from the authenticated access key via c.Locals("sigv4.accountId") and passes it to all IAM service methods. Root user (account 000000000000) bypasses policy evaluation entirely.
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-user |
--user-name, --path |
--tags, --permissions-boundary |
None | Gateway validates UserName non-empty → IAMService.CreateUser(accountID, input) → generates AIDA-prefixed UserId → stores in KV at key {accountID}.{userName} → ARN includes real account ID → conditional create for race safety |
1. Create user 2. Duplicate name in same account (EntityAlreadyExists) 3. Same name in different account (succeeds) 4. Missing username (InvalidInput) 5. Default path is / |
DONE |
get-user |
--user-name |
— | User must exist in caller's account | Gateway validates UserName → IAMService.GetUser(accountID, input) → reads from KV at {accountID}.{userName} → returns User with ARN, Path, CreateDate |
1. Get existing user 2. Get non-existent user (NoSuchEntity) 3. Cannot see user in different account (NoSuchEntity) |
DONE |
list-users |
--path-prefix |
--max-items, --marker |
None | IAMService.ListUsers(accountID, input) → prefix-scans KV for {accountID}.* → filters by path prefix if specified → returns user list scoped to caller's account |
1. List all users in account 2. List with path prefix filter 3. Empty list 4. Does not show other accounts' users |
DONE |
delete-user |
--user-name |
— | User must exist, no access keys attached | Gateway validates UserName → IAMService.DeleteUser(accountID, input) → checks user has no access keys → deletes from KV | 1. Delete user with no keys 2. Delete user with keys attached (DeleteConflict) 3. Delete non-existent user (NoSuchEntity) |
DONE |
IAM - Access Key Management
Access keys are globally unique (keyed by AccessKeyID) but associated with an account. Max 2 keys per user. Secrets encrypted with AES-256-GCM using master key.
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-access-key |
--user-name |
— | User must exist | IAMService.CreateAccessKey(accountID, input) → generates AKIA-prefixed key ID + random secret → encrypts secret with master key → stores in KV (globally unique key ID) → updates user's AccessKeys list → returns plaintext secret (one-time only) | 1. Create key for user (AKIA prefix) 2. Max 2 keys per user (LimitExceeded on 3rd) 3. Non-existent user (NoSuchEntity) 4. Secret only returned on creation |
DONE |
list-access-keys |
--user-name |
--max-items, --marker |
None | IAMService.ListAccessKeys(accountID, input) → reads user's AccessKeys list → fetches each key's metadata → returns key IDs and status (no secrets) | 1. List keys for user 2. No keys returns empty list 3. Shows Status (Active/Inactive) but not secrets |
DONE |
delete-access-key |
--access-key-id, --user-name |
— | Key must exist | IAMService.DeleteAccessKey(accountID, input) → removes key from global KV → removes key ID from user's AccessKeys list | 1. Delete existing key 2. Delete non-existent key (NoSuchEntity) |
DONE |
update-access-key |
--access-key-id, --user-name, --status (Active/Inactive) |
— | Key must exist | IAMService.UpdateAccessKey(input) → reads key from global KV (key ID is globally unique, no accountID needed) → updates Status field → inactive keys rejected at SigV4 auth | 1. Deactivate key (Inactive) 2. Reactivate key (Active) 3. Non-existent key (NoSuchEntity) 4. Inactive key → auth returns InvalidClientTokenId |
DONE |
IAM - Policy Management
Policies are account-scoped. KV key is {accountID}.{policyName}. Policy documents require Version: "2012-10-17" and valid Statement array. Supports wildcard action matching (ec2:*, ec2:Describe*, *). Evaluation order: explicit Deny > explicit Allow > implicit Deny.
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
create-policy |
--policy-name, --policy-document, --path, --description |
--tags |
None | Gateway validates PolicyName and PolicyDocument non-empty → IAMService.CreatePolicy(accountID, input) → validates JSON (Version, Statement with Effect/Action/Resource) → generates ANPA-prefixed PolicyId → stores at {accountID}.{policyName} → ARN includes real account ID |
1. Create with valid document 2. Malformed JSON (MalformedPolicyDocument) 3. Missing Effect/Action/Resource (MalformedPolicyDocument) 4. Duplicate name in same account (EntityAlreadyExists) 5. Same name in different account (succeeds) 6. With path and description |
DONE |
get-policy |
--policy-arn |
— | Policy must exist | Gateway validates PolicyArn → extracts account ID and policy name from ARN → IAMService.GetPolicy(accountID, input) → returns policy metadata (no document) with AttachmentCount | 1. Get existing policy 2. Non-existent policy (NoSuchEntity) 3. Returns metadata only, not document |
DONE |
get-policy-version |
--policy-arn, --version-id |
— | Policy must exist | Gateway validates PolicyArn and VersionId → IAMService.GetPolicyVersion(accountID, input) → returns PolicyVersion with URL-encoded Document, VersionId, IsDefaultVersion | 1. Get version v1 (returns document) 2. Non-existent version (NoSuchEntity) 3. Non-existent policy (NoSuchEntity) |
DONE |
list-policies |
— | --scope, --only-attached, --path-prefix, --max-items, --marker |
None | IAMService.ListPolicies(accountID, input) → prefix-scans KV for {accountID}.* → returns all policies in caller's account |
1. List all policies in account 2. Empty list 3. Does not show other accounts' policies |
DONE |
delete-policy |
--policy-arn |
— | Policy must exist, not attached to any user | Gateway validates PolicyArn → IAMService.DeletePolicy(accountID, input) → checks no users have this policy attached (scans account's users) → deletes from KV | 1. Delete unattached policy 2. Delete attached policy (DeleteConflict) 3. Delete non-existent (NoSuchEntity) |
DONE |
IAM - Policy Attachment
| Command | Implemented Flags | Missing Flags | Prerequisites | Basic Logic | Test Cases | Status |
|---|---|---|---|---|---|---|
attach-user-policy |
--user-name, --policy-arn |
— | User and policy must exist in caller's account | Gateway validates both params → IAMService.AttachUserPolicy(accountID, input) → verifies policy exists → adds policy ARN to user's AttachedPolicies list → idempotent (re-attach succeeds without duplicate) | 1. Attach policy to user 2. Already attached (idempotent, no error) 3. Non-existent user (NoSuchEntity) 4. Non-existent policy (NoSuchEntity) |
DONE |
detach-user-policy |
--user-name, --policy-arn |
— | Policy must be attached to user | Gateway validates both params → IAMService.DetachUserPolicy(accountID, input) → removes policy ARN from user's AttachedPolicies list | 1. Detach attached policy 2. Policy not attached (NoSuchEntity) 3. Non-existent user (NoSuchEntity) |
DONE |
list-attached-user-policies |
--user-name |
--path-prefix, --max-items, --marker |
User must exist | IAMService.ListAttachedUserPolicies(accountID, input) → reads user's AttachedPolicies list → returns policy ARNs and names | 1. List attached policies 2. No policies returns empty list |
DONE |
IAM - Policy Evaluation
The gateway evaluates IAM policies on every request (except root user bypass). Policy evaluation is internal — not an AWS API.
| Feature | Description | Status |
|---|---|---|
| Root user bypass | Root user (000000000000:root) skips all policy checks |
DONE |
| Default deny | Users with no attached policies are denied all actions | DONE |
| Explicit allow | Matching Allow statement grants access | DONE |
| Explicit deny | Deny statements override Allow (deny wins) | DONE |
| Wildcard matching | * matches all, ec2:* matches all EC2, ec2:Describe* matches prefix |
DONE |
| Account-scoped evaluation | Policies resolved from caller's account context | DONE |
| EC2 action mapping | 54 EC2 actions mapped to IAM policy strings | DONE |
| IAM action mapping | 16 IAM actions mapped to IAM policy strings | DONE |