The three official CLIs drive Feint without any
modification, with one configuration nuance per client that feint env reports
rather than leaving you to discover it. This page first installs scw and
octl, then walks through the common commands of all three providers, then the families that go further: block storage on
Scaleway and Exoscale, the golden image sequence, and quotas.
What you will get
By the end of this page, the three official CLIs will have created resources on
your machine: a server on Scaleway, a Net on Outscale, a volume on
Exoscale if you have the exo CLI, each listed by its own command.
- Install
scwandoctlat a pinned version, with no account, and check the installation. - Point each official CLI at the emulator without copying a variable.
- Read what the API returns when it differs from what the CLI displays.
- Create a block volume on Scaleway, and on Exoscale if
exois installed. - Walk the golden image sequence, in the order the API imposes.
Prerequisites
The Feint emulator installed, a Linux
terminal, curl, sha256sum and jq (which extracts a field from JSON
output, used by the golden image sequence). To verify octl's signature,
cosign 2.0 or newer is required. The exo CLI from Exoscale is
optional: this page does not install it.
Installing scw and octl
scw and octl install without a cloud account, at a version you choose and
check. This section assumes the Feint emulator is
installed. Both CLIs are standalone
binaries; installing needs no login, since feint env supplies fictitious
credentials further down. The detailed pages on scw and octl cover other
systems, real-account login and profiles; only the bare minimum is given here.
The exo CLI stays optional: its section assumes it is already installed and configured, which this page does not cover.
For scw, the pinned version is 2.65.1. The Linux amd64 binary is downloaded
with the SHA256SUMS file of the same release, and sha256sum checks integrity
before any installation.
VERSION=2.65.1curl -fsSLO "https://github.com/scaleway/scaleway-cli/releases/download/v${VERSION}/scaleway-cli_${VERSION}_linux_amd64"curl -fsSLO "https://github.com/scaleway/scaleway-cli/releases/download/v${VERSION}/SHA256SUMS"sha256sum --ignore-missing -c SHA256SUMSsudo install -m 0755 "scaleway-cli_${VERSION}_linux_amd64" /usr/local/bin/scwscw versionThe binary's line must show OK, and scw version must show 2.65.1.
FAILED means the file is tampered with: do not install it.
For octl, the pinned version is 0.0.33. The static binary is downloaded from
the release of the outscale/octl repository, with the checksums file of the same
release. Its binaries are signed: the signature covers the
checksums file, which cosign verifies before sha256sum checks the binary. The
block below chains the two.
VERSION=0.0.33base="https://github.com/outscale/octl/releases/download/v${VERSION}"curl -fsSL -o octl_Linux_x86_64 "$base/octl_Linux_x86_64"curl -fsSLO "$base/octl_${VERSION}_checksums.txt"curl -fsSLO "$base/octl_${VERSION}_checksums.txt.sigstore.json"cosign verify-blob \ --bundle "octl_${VERSION}_checksums.txt.sigstore.json" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ --certificate-identity-regexp "^https://github.com/outscale/octl/" \ "octl_${VERSION}_checksums.txt"sha256sum --ignore-missing -c "octl_${VERSION}_checksums.txt"sudo install -m 0755 octl_Linux_x86_64 /usr/local/bin/octloctl --versioncosign must answer Verified OK, the binary's line must show OK, and
octl --version must show octl version v0.0.33 followed by the list of embedded libraries. The octl version form,
without dashes, does not exist: it answers unknown command "version". Two
precautions protect any real account during tests: run octl with
--no-upgrade, so it does not look for a new version, and, if a real profile
exists on your machine, give it a fictitious configuration file with
--config rather than letting the tool read the real one. That file must
contain a profile named default (format described in the octl page): an empty
file answers no default profile found.
Scaleway with scw
feint env scaleway produces the environment the official client needs. The
command writes only export lines on standard output, which makes eval safe;
remarks go to standard error.
eval "$(feint env scaleway)"scw instance server create name=web-01 type=DEV1-S image=ubuntu_jammy zone=fr-par-1scw instance server list zone=fr-par-1The variables produced point SCW_API_URL at http://127.0.0.1:4599 and
supply well-formed but fictitious credentials. That is enough: the SDKs
validate the shape of credentials client-side before sending anything, and the
emulator verifies no signature.
The server you create shows up immediately as running in the list, while the
protocol returns stopped at creation time. The explanation is not in the
emulator: scw instance server create sends a start action right after the
create. Queried directly, the emulator answers what Scaleway would:
{"name":"initial-state","state":"stopped"}Scaleway block storage
Scaleway serves block: volumes, their snapshots and the type catalogue. Two
formatting details are worth knowing because they fail the command before it even
reaches the emulator.
scw block volume create name=data from-empty.size=10G perf-iops=5000 zone=fr-par-1{"name":"data","type":"sbs","size":10000000000,"class":"sbs","iops":5000,"status":"available"}The size is written as 10G, a byte count being refused by the CLI where the
instance command accepts it, and perf-iops is mandatory. One implementation
detail is worth flagging for anyone writing a script: scw queries
/block/v1alpha1 where the Terraform provider queries /block/v1. Both
spellings answer, which saves you from choosing between two official clients of
the same cloud.
The golden image sequence
A golden image is a reference disk, prepared once, from which you launch identical machines. Its sequence is served end to end: a snapshot (a photograph of a volume at a given instant) of the volume, an image cut from that snapshot, then deletion in the order the API imposes.
You first need a source volume, and a volume that nothing ever carried
cannot be snapshotted: the real cloud refuses the snapshot of a disk that was
never attached (cannot create a RO disk from an empty disk), and the emulator
reproduces that refusal. The next three commands create a builder machine, a
local volume whose identifier they store in $vol, then attach it to
the machine. The identifier sits under the volume key of the JSON answer, hence
.volume.id and not .id:
srv=$(scw instance server create name=builder type=DEV1-S image=ubuntu_jammy zone=fr-par-1 -o json | jq -r .id)vol=$(scw instance volume create name=source volume-type=l_ssd size=10G zone=fr-par-1 -o json | jq -r .volume.id)scw instance server attach-volume server-id="$srv" volume-id="$vol" zone=fr-par-1Then chain. Each answer wraps the created object under a key in the same way
(snapshot, image):
snap=$(scw instance snapshot create name=snap-source volume-id="$vol" zone=fr-par-1 -o json | jq -r .snapshot.id)img=$(scw instance image create name=golden-image snapshot-id="$snap" arch=x86_64 zone=fr-par-1 -o json | jq -r .image.id)scw instance image list zone=fr-par-1 -o json | jq -c '[.[].name]'["golden-image"]The image you create appears in the list, and the deletion order is the real cloud's: as long as the image exists, the snapshot it is cut from refuses to go, with a message naming the culprit.
scw instance snapshot delete "$snap" zone=fr-par-1scaleway-sdk-go: precondition failed: resource is still in use, the image 7f1db7e3-… is cut from this snapshotThat is exactly the order Terraform walks when a single plan removes both, which makes this refusal useful rather than annoying. To clean up, delete the image, then the snapshot, then the builder machine, and finally the volume:
scw instance image delete "$img" zone=fr-par-1 with-snapshots=falsescw instance snapshot delete "$snap" zone=fr-par-1scw instance server terminate "$srv" zone=fr-par-1 with-ip=true with-block=truescw instance volume delete "$vol" zone=fr-par-1An image cut here starts nothing, though: a disk's bytes are the one thing an emulator cannot provide, and the limit is reported at boot rather than at creation.
Outscale with octl
Outscale's official client is octl, which replaces oapi-cli,
archived upstream. It reads the environment, and feint env outscale gives
it the right address form directly.
eval "$(feint env outscale)"octl iaas net create --ip-range 10.0.0.0/16octl iaas net listResolving alias to [octl iaas api CreateNet --output yaml --single --IpRange=10.0.0.0/16]DhcpOptionsSetId: dopt-4d168602IpRange: 10.0.0.0/16NetId: vpc-ac76685bState: availableTags: []Tenancy: defaultThe first line deserves a word: octl announces that it resolves an alias to
the underlying API action. Aliases cover the common cases, and the
generic form stays available for any action, which makes it the direct
replacement for the older commands:
octl iaas api CreatePublicIpThe output is the created object followed by the ResponseContext (the address
and identifiers of your run differ from the example):
{ "PublicIp": { "PublicIp": "198.51.100.1", "PublicIpId": "eipalloc-c1efc8bc", "Tags": [] }, "ResponseContext": { "RequestId": "94aef720-f05c-4e44-83e4-dd551fc7081c" }}A key pair is created differently, because the real API and the emulator do
not read the key the same way. First generate a local pair: ssh-keygen writes
the private key to ./id and the public key to ./id.pub.
ssh-keygen -t ed25519 -f ./id -N ''The Outscale API expects the public key Base64-encoded in the PublicKey
field (reference for the CreateKeypair action on docs.outscale.com). With
octl, you therefore do not paste the key into the command: you give it the
file name, which it reads and encodes itself.
--PublicKey="$(cat ./id.pub)" fails, because octl takes that text for a file
name (no such file or directory). Against the real Outscale, the command is:
octl iaas api CreateKeypair --KeypairName=lab --PublicKey ./id.pubFeint 0.13.0 departs from the real API here: it expects the key in plain
text and rejects the Base64 form with error 4001:
PublicKey is not an OpenSSH public key. Against the emulator, the octl
command above therefore fails, and you send the request directly with curl and jq, leaving the
key in plain text:
jq -n --rawfile k ./id.pub '{KeypairName:"lab",PublicKey:$k}' \ | curl -s -X POST http://127.0.0.1:4599/api/v1/CreateKeypair -d @-{"Keypair":{"KeypairFingerprint":"57:72:5e:b8:3f:d0:72:9a:32:fc:be:44:95:6f:23:16","KeypairId":"key-c6290953425c4bb18ef2cc152316d58f","KeypairName":"lab","KeypairType":"ssh-ed25519","Tags":[]}}The full response also carries a ResponseContext. To check it, compare the
fingerprint with your key's: ssh-keygen -lf ./id.pub -E md5 prints the same
16 bytes in hexadecimal (yours differs from the example, since the key is yours).
In JSON output, the same list shows the full Outscale envelope, the one
your code will have to decode: an array named after the resource type, and a
ResponseContext carrying the request identifier.
{ "Nets":[ { "IpRange":"10.0.0.0/16", "NetId":"vpc-3a31b260", "State":"available", "Tags":[], "Tenancy":"default" } ], "ResponseContext":{"RequestId":"3b25f637-a288-4839-b4aa-c3b1a709f6bb"}}Exoscale with exo
The exo CLI takes its endpoint from its configuration file, where
endpoint must be http://127.0.0.1:4599/v2. Note the /v2 suffix, which is
part of the address on this provider.
eval "$(feint env exoscale)"exo compute instance-type list| ID | FAMILY | SIZE ||--------------------------------------|----------|-------|| 21624abb-764e-4def-81d7-9fc54b5957fb | standard | tiny || b6cd1ff5-3a2f-4e9d-a4d1-8988c1191fe8 | standard | small |Two instance types only: that is the emulator's frozen catalogue, not Exoscale's inventory.
Exoscale block storage
Exoscale's block storage is served: volumes, snapshots, resizing and the attachment chain, all driven by the official CLI.
exo compute block-storage create lab-vol --size 10 --zone "$EXOSCALE_ZONE"exo compute block-storage list| ID | NAME | ZONE | SIZE | STATE ||--------------------------------------|---------|---------|--------|----------|| 7d4ca985-3365-44e4-aa5c-2ade17d51453 | lab-vol | ch-dk-2 | 10 GiB | detached |The detached state is that of a volume nothing carries yet. Every number a
volume publishes says the storage holds no bytes: the size is a declaration,
not an allocation, and What Feint proves
states it in black and white.
Quotas
Quotas answer too, but they prove nothing: the limit shown is a fixed value
of the emulator, like the catalogue, the usage shown is what the emulator has
recorded, and no quota refusal is issued, even beyond the displayed limit.
Measured with Feint 0.13.0 and exo: exo limits on a fresh emulator reports 0
instances, 3 after three creates (exo compute instance create), then 105
out of a maximum of 100 after 105 creates, without a single error. A deletion
decrements the usage (104). The two other lines stayed at 0 in this scenario (no
snapshot or template was created); on the Outscale side, reading quotas (ReadQuotas) is not served and answers
OperationNotEmulated. What Feint proves
says the same: no quota-exhaustion test makes sense here.
| RESOURCE | USED | MAX ||----------------------------|------|-----|| Compute instances | 0 | 100 || Compute instance snapshots | 0 | 100 || Compute instance templates | 0 | 100 |Key takeaways
scwandoctlinstall without an account, at a pinned version (2.65.1 and 0.0.33) checked withscw versionandoctl --version.feint env <provider>produces the expected environment;evalis enough forscw.octlreads the environment with an address already carrying/api/v1, andexoreads its configuration file with the/v2suffix.- The CLI may add calls: a server shown as
runningwas started byscw, not by the emulator. - Block storage is served on Scaleway and Exoscale, with sizes that cover no bytes.
- The golden image sequence respects the real cloud's deletion order, the one Terraform walks.
- Quotas show a fixed limit and a recorded usage, and never refuse a creation.