From 88cbdb213f97b4a1af6f04cb60459ad3dc37a12e Mon Sep 17 00:00:00 2001 From: Pandelis Zembashis
Date: Sat, 24 Oct 2026 17:03:41 -0700 Subject: [PATCH] Use composal.com while preserving explicit API origins --- README.md | 2 +- action.yml | 2 +- builder.mjs | 8 +++++--- builder.test.mjs | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1596623..940ebe5 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Authenticate to your registry before selecting the remote context: the gateway d An organization admin must request Gen2 builder activation first. Composal provisions a separate `ci-gen2` builder through the organization API with `backend: gen2`; an existing Classic builder cannot be converted in place. The token needs membership in that organization. Setup fails if the builder is missing, unavailable, or refused; it never falls back to a local build or creates a cache accidentally. -Inputs are `token` and `org` (required), `builder` (default `main-gen2`), `wake-timeout` (default `900` seconds, range `30`–`3600`), `use` (default `true`), `api-url` (default `https://composal.ai`), and `install-cli` (default `auto`). Automatic mode reuses a preinstalled stable `com` version 1.17.92 or newer within major version 1, including the CLI baked into Gen2 runners; otherwise it installs the latest release privately and adds it to the job PATH. Set `install-cli: 'true'` to always install, or `'false'` to require a compatible installed CLI. Docker and Buildx must be installed on the runner. Outputs are `builder`, `context`, and `driver` (`docker`). Each job isolates CLI supervisor state in its temporary directory. Setup logs the CLI version, checks gateway health and waits for the shared builder to become ready before opening native Docker connections. Cold starts can take several minutes; `wake-timeout` controls this wait. Progress includes the builder state and generation. Setup requests a retry when it finds a builder already starting, so a failed earlier wake can resume. Canceling a job disconnects only that job; it does not cancel the shared wake. It retries transient status-read server errors, then checks the remote daemon with bounded, retryable probes before selecting the context and builder for subsequent job steps using `DOCKER_CONTEXT` and `BUILDX_BUILDER`. CLI setup verifies that Buildx is installed; the build performs BuildKit discovery once, without a redundant setup inspection. It leaves Docker's saved defaults unchanged and restores the previous job environment during cleanup. +Inputs are `token` and `org` (required), `builder` (default `main-gen2`), `wake-timeout` (default `900` seconds, range `30`–`3600`), `use` (default `true`), `api-url` (default `https://composal.com`), and `install-cli` (default `auto`). Automatic mode reuses a preinstalled stable `com` version 1.17.92 or newer within major version 1, including the CLI baked into Gen2 runners; otherwise it installs the latest release privately and adds it to the job PATH. Set `install-cli: 'true'` to always install, or `'false'` to require a compatible installed CLI. Docker and Buildx must be installed on the runner. Outputs are `builder`, `context`, and `driver` (`docker`). Each job isolates CLI supervisor state in its temporary directory. Setup logs the CLI version, checks gateway health and waits for the shared builder to become ready before opening native Docker connections. Cold starts can take several minutes; `wake-timeout` controls this wait. Progress includes the builder state and generation. Setup requests a retry when it finds a builder already starting, so a failed earlier wake can resume. Canceling a job disconnects only that job; it does not cancel the shared wake. It retries transient status-read server errors, then checks the remote daemon with bounded, retryable probes before selecting the context and builder for subsequent job steps using `DOCKER_CONTEXT` and `BUILDX_BUILDER`. CLI setup verifies that Buildx is installed; the build performs BuildKit discovery once, without a redundant setup inspection. It leaves Docker's saved defaults unchanged and restores the previous job environment during cleanup. If a startup attempt fails, setup reports its generation, stage and failure reason immediately. A fresh job can retry a failed startup against the same builder and retained cache. Simultaneous jobs share that startup; canceling one job keeps the shared startup running. diff --git a/action.yml b/action.yml index 998bdff..cb3a030 100644 --- a/action.yml +++ b/action.yml @@ -22,7 +22,7 @@ inputs: default: 'true' api-url: description: Composal API origin. - default: https://composal.ai + default: https://composal.com install-cli: description: 'auto reuses a compatible installed CLI, otherwise installs privately; true always installs; false requires an installed CLI.' default: 'auto' diff --git a/builder.mjs b/builder.mjs index ddf08a9..85b1b18 100644 --- a/builder.mjs +++ b/builder.mjs @@ -61,7 +61,7 @@ export async function setup(env = process.env, run = command, wait = delay) { const builder = input(env, 'builder', 'main-gen2'); if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(org)) throw new Error('org is required and must be a slug or public ID'); if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(builder)) throw new Error('builder must be a slug or public ID'); - const api = new URL(input(env, 'api-url', 'https://composal.ai')); + const api = new URL(input(env, 'api-url', 'https://composal.com')); if (api.protocol !== 'https:' || api.username || api.password || api.pathname !== '/' || api.search || api.hash) { throw new Error('api-url must be an HTTPS origin'); } @@ -95,8 +95,10 @@ export async function setup(env = process.env, run = command, wait = delay) { const directory = join(env.RUNNER_TEMP, `com-docker-builder-${randomUUID()}`); await mkdir(directory, { recursive: true, mode: 0o700 }); const script = join(directory, 'install.sh'); - await run('curl', ['-fsSL', '--max-time', '60', 'https://dl.composal.ai/install.sh', '-o', script], cleanEnv); - await run('sh', [script], { ...cleanEnv, VEX_INSTALL_DIR: directory }, 300_000); + const downloadOrigin = ['https://composal.ai', 'https://vex.sc', 'https://api.vex.sc'].includes(api.origin) + ? 'https://dl.composal.ai' : 'https://dl.composal.com'; + await run('curl', ['-fsSL', '--max-time', '60', `${downloadOrigin}/install.sh`, '-o', script], cleanEnv); + await run('sh', [script], { ...cleanEnv, VEX_INSTALL_DIR: directory, VEX_API_BASE_URL: api.origin }, 300_000); cli = join(directory, 'com'); if (!env.GITHUB_PATH) throw new Error('GITHUB_PATH is required'); await appendFile(env.GITHUB_PATH, `${dirname(cli)}\n`); diff --git a/builder.test.mjs b/builder.test.mjs index 3ab1b87..59aaada 100644 --- a/builder.test.mjs +++ b/builder.test.mjs @@ -468,3 +468,19 @@ test('explicit preinstalled mode rejects an unsupported CLI without installing', await assert.rejects(setup(f.env, run), /1.17.92 or newer/); assert.equal(f.calls.length, 0); }); + + +test('installation and CLI calls follow the selected hosted API', async t => { + for (const [api, cdn] of [ + ['https://composal.com', 'https://dl.composal.com'], + ['https://composal.ai', 'https://dl.composal.ai'], + ['https://private.example', 'https://dl.composal.com'], + ]) { + const f = await fixture(t, { 'INPUT_INSTALL-CLI': 'true', 'INPUT_API-URL': api }); + await setup(f.env, f.run); + const download = f.calls.find(c => c.executable === 'curl'); + assert.ok(download.args.includes(`${cdn}/install.sh`)); + assert.equal(f.calls.find(c => c.executable === 'sh').env.VEX_API_BASE_URL, api); + assert.equal(f.calls.find(c => c.args[1] === 'status').env.VEX_API_BASE_URL, api); + } +});