fix(ubicloud): ubi-runner up/run -S accepts only Ubicloud's storage sizes, before any API call (A11)

Files: scripts/ubicloud/ubi-runner.sh, test/ubicloud-runner.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-14 23:58:35 +00:00
1 parent 47d639abff
commit bfbb1dfb5e
2 files changed
+40 -3

No files matched your search

+15 -3
View File
@@ -51,6 +51,7 @@ API_TIMEOUT="${UBI_API_TIMEOUT:-120}"
CREATE_GRACE="${UBI_CREATE_GRACE:-180}"
DOWN_TIMEOUT="${UBI_DOWN_TIMEOUT:-900}"
POLL="${UBI_POLL_SECONDS:-5}"
ALLOWED_STORAGE="${UBI_ALLOWED_STORAGE:-320 640}"
PREFIX="ubirun"
if [ -n "${UBI_VM_RECORD:-}" ]; then VM_RECORD="$UBI_VM_RECORD"
elif [ -d "$HOME/.capy/work" ]; then VM_RECORD="$HOME/.capy/work/ubi-runner/vms.tsv"
@@ -229,6 +230,14 @@ new_name() { echo "$PREFIX-$OWNER-$(date +%s)-$(head -c4 /dev/urandom | od -An -
valid_name() { [[ "$1" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; }
# check_storage GIB: Ubicloud accepts only these disk sizes; refuse before any API call.
check_storage() {
case " $ALLOWED_STORAGE " in
*" $1 "*) [ -n "$1" ] && return 0 ;;
esac
die "invalid storage '$1' GiB (allowed: $ALLOWED_STORAGE; set UBI_ALLOWED_STORAGE when Ubicloud adds sizes)"
}
UP_NAME=""
UP_READY=""
cmd_up() {
@@ -238,7 +247,7 @@ cmd_up() {
-s|--size) size=$2; shift 2 ;;
-l|--location) location=$2; shift 2 ;;
-n|--name) name=$2; shift 2 ;;
-S|--storage) storage=$2; shift 2 ;;
-S|--storage) check_storage "$2"; storage=$2; shift 2 ;;
-b|--image) image=$2; shift 2 ;;
*) die "up: unknown option $1" ;;
esac
@@ -430,7 +439,8 @@ cmd_run() {
local up_args=() src=. dest="" setup="" keep=0 pulls=() envs=()
while [ $# -gt 0 ]; do
case $1 in
-s|--size|-l|--location|-n|--name|-S|--storage|-b|--image) up_args+=("$1" "$2"); shift 2 ;;
-S|--storage) check_storage "$2"; up_args+=("$1" "$2"); shift 2 ;;
-s|--size|-l|--location|-n|--name|-b|--image) up_args+=("$1" "$2"); shift 2 ;;
--src) src=$2; shift 2 ;;
--dest) dest=$2; shift 2 ;;
--setup) setup=$2; shift 2 ;;
@@ -498,6 +508,7 @@ usage: ubi-runner.sh <command> [args]
up + sync + setup + command + pull + destroy; exits with COMMAND's status
up [-s SIZE] [-l LOCATION] [-n NAME] [-S STORAGE_GIB] [-b IMAGE]
create a VM and wait for SSH; prints its name
(STORAGE_GIB: one of $ALLOWED_STORAGE)
ssh NAME [COMMAND] shell or login-shell command on the VM (user ubi, passwordless sudo)
sync NAME [SRC] [DEST] stream a checkout (tracked + untracked-unignored + .git)
pack [SRC] write that checkout tarball to stdout
@@ -516,7 +527,8 @@ VMs are named $PREFIX-<owner>-<epoch>-<suffix>; set UBI_OWNER (e.g. your thread
code) to tag them, otherwise a per-machine id is used.
defaults: size=$DEFAULT_SIZE location=$DEFAULT_LOCATION (env UBI_SIZE, UBI_LOCATION)
env: UBI_GC_HOURS (unset/0: no sweep on up), UBI_CREATE_GRACE (${CREATE_GRACE}s),
UBI_DOWN_TIMEOUT (${DOWN_TIMEOUT}s), UBI_API_TIMEOUT (${API_TIMEOUT}s), UBI_VM_RECORD (scratch VM-id file)
UBI_DOWN_TIMEOUT (${DOWN_TIMEOUT}s), UBI_API_TIMEOUT (${API_TIMEOUT}s), UBI_VM_RECORD (scratch VM-id file),
UBI_ALLOWED_STORAGE (the -S sizes Ubicloud accepts; default "320 640")
EOF
}
+25
View File
@@ -92,6 +92,31 @@ describe('ubicloud free-suite runner', () => {
expect(result.stderr.toString()).toContain('UBICLOUD_API_KEY is not set');
expect(result.stdout.toString()).toBe('');
});
test('up and run accept only the storage sizes Ubicloud offers, before any API call; UBI_ALLOWED_STORAGE widens the list', () => {
const root = mkdtempSync(join(tmpdir(), 'ubi-storage-'));
try {
const bin = join(root, 'bin'), log = join(root, 'curl.log');
mkdirSync(bin);
writeFileSync(join(bin, 'curl'), `#!/usr/bin/env bash\necho "curl $*" >> ${JSON.stringify(log)}\necho 000\n`);
chmodSync(join(bin, 'curl'), 0o755);
const env = { ...process.env, PATH: `${bin}:${process.env.PATH}`, UBICLOUD_API_KEY: 'offline', UBI_RUNNER_STATE: join(root, 'state'), UBI_VM_RECORD: join(root, 'vms.tsv'), UBI_OWNER: 't' };
const ubi = (args: string[], extra: Record<string, string> = {}) => Bun.spawnSync(['bash', join(DIR, 'ubi-runner.sh'), ...args], { env: { ...env, ...extra }, timeout: 10_000 });
for (const args of [['up', '-S', '100'], ['run', '--storage', '1000', '--', 'true'], ['up', '-S', '']]) {
const r = ubi(args);
expect(r.exitCode).toBe(1);
expect(r.stderr.toString()).toContain('(allowed: 320 640; set UBI_ALLOWED_STORAGE when Ubicloud adds sizes)');
}
expect(existsSync(log)).toBe(false);
for (const [size, extra] of [['320', {}], ['1280', { UBI_ALLOWED_STORAGE: '320 640 1280' }]] as const) {
const accepted = ubi(['run', '-S', size, '--bogus'], extra);
expect(accepted.stderr.toString()).toContain('run: unknown option --bogus');
}
expect(existsSync(log)).toBe(false);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});
describe('ubi-runner teardown is confirmed and never sweeps other runners by default', () => {