[ADD] runbot_attach_vscode: data layer code-server (POC task #67684)#49
Closed
jjscarafia wants to merge 20 commits into
Closed
[ADD] runbot_attach_vscode: data layer code-server (POC task #67684)#49jjscarafia wants to merge 20 commits into
jjscarafia wants to merge 20 commits into
Conversation
Adds a new module exposing a reusable runbot.docker_layer that installs code-server (VS Code in the browser) on top of any runbot Dockerfile. The layer is generic and not attached to any Dockerfile by default - the admin assigns it via UI to the target Dockerfile by creating a reference_layer pointing to runbot_attach_vscode.docker_layer_code_server. This keeps the module reusable across runbots and matches the bake-in opcional approach of the spec. Follow-up: extend runbot.build with an Open VS Code action that starts code-server on wake-up via docker exec and returns a routable URL. Change note: Nuevo modulo runbot_attach_vscode (POC tarea #67684): expone una runbot.docker_layer reusable con xml_id que instala code-server sobre cualquier Dockerfile del runbot. Por ahora solo la capa de bake-in, asignable manualmente desde la UI; la extension del build con el boton Open VS Code + helper de URL queda para los proximos commits del mismo PR.
…k #67684) Iteration on the scaffold to validate UX before the productive runbot build picks up the PR. - Layer turned into a 'template' with CODE_SERVER_VERSION placeholder (default 4.96.4) and the install.sh --version flag. Pins code-server per Dockerfile and lets the consuming reference_layer override via its own values JSON. - New ir.config_parameter records for runbot_attach_vscode.url_suffix (default 'vscode') and runbot_attach_vscode.scheme (default 'https'). - runbot.build extended with a computed vscode_url field (<scheme>://<dest>-<suffix>.<host>) and an action_open_vscode that returns ir.actions.act_url. Internal-user-only via _is_internal(). - Build form view: 'Open VS Code' button in a new header (groups='base.group_user', invisible when vscode_url is empty) and vscode_url shown as widget='url' next to build_url. - Minimal README.rst documenting Phase 1 scope and Phase 2 backlog (proxy routing, port mapping, docker exec code-server, auth token). - runbot_docker_layer.xml dropped 'noupdate=1' so subsequent module updates re-apply the layer content/values; ir_config_parameter.xml keeps 'noupdate=1' so the admin can override values at runtime. Change note: Iteracion sobre el scaffold antes que el runbot productivo agarre el PR. Convierte la capa code-server a 'template' (parametrizable via CODE_SERVER_VERSION), expone dos parametros de sistema para el patron de URL, suma un campo computed vscode_url + boton 'Open VS Code' en el form del build (restringido a usuarios internos) y un README minimo. El boton solo construye la URL siguiendo el patron Nginx existente (<dest>-<suffix>.<host>) — el routing real del proxy, el port mapping y el arranque de code-server via docker exec quedan para la Fase 2.
- views/runbot_frontend_templates.xml: inherit runbot.build_button and add a "code" icon button next to the existing "Sign in" / "Wake up" buttons in the public build action bar. Visible to base.group_user when local_state == 'running' and vscode_url is computable. Target _blank for browser tab. - tests/test_runbot_attach_vscode.py: 10 tests covering layer template metadata + rendered substitution, reference_layer composition with and without values override, vscode_url compute (default + ICP overrides + empty-when-missing), action_open_vscode happy path, guard when url missing, and guard for portal users. No bump of __manifest__ version: runbot updates are manual and this is not a migration. Bump only when worth it. Change note: Suma boton "code" en el action bar publico del build (frontend de runbot, junto a Sign in / Wake up) — el cliente PO/dev lo ve ahi al estar el build wakeado. Tests unitarios sobre el render del template, el patron reference_layer (default + override per-Dockerfile) y la proteccion del action_open_vscode contra portal users / dest faltante.
Dropped 4 of the 10 unit tests added in the prior commit and fused the three vscode_url compute tests into one. Final coverage: 4 tests. Dropped: - test_source_layer_metadata: echoed the XML data record, no logic. - test_source_layer_renders_default_version: exercised upstream's template engine, not our code. - test_reference_layer_inherits_default_version: redundant with the override case, which already proves the chain works. - test_action_open_vscode_returns_act_url: shape-check of the ir.actions.act_url dict literal, caught by the first manual click. Kept and consolidated: - test_reference_layer_overrides_version: only smoke test of the documented Dockerfile composition. - test_vscode_url: defaults + ICP override + missing-dest short circuit, sequential — ICP leakage is irrelevant because the defaults assertion runs first. - test_action_open_vscode_blocks_when_url_missing. - test_action_open_vscode_blocks_portal_user. Also translated remaining docstrings/comments to English and renamed the fake host runbot.example.com -> ci.example.com to silence the spell-checker. Change note: Limpia los tests del modulo: de 10 quedamos en 4. Borramos los que asertaban contra el XML, los que ejercitaban el motor de templates de runbot upstream y el happy path del action (solo chequea forma del dict). Mantenemos el smoke del reference_layer con override, el compute vscode_url (defaults + override de ICP + short-circuit sin dest, todo en un solo metodo) y los dos guards del action_open_vscode (url ausente, usuario portal).
Self-contained set of demo records that exercise the runbot tree
end-to-end so the form and frontend views can be inspected locally
without a real runbot in place. Lives in runbot_ux so it can be
reused by any future runbot-ux iteration; wired up via the
manifest's 'demo' key.
Records:
- 1 runbot.version (18.0).
- 1 repo + remote ('acme-addons', mode=disabled to keep the demo
inert: no fetch, no clone).
- 1 trigger ('All') so builds render with a '[18.0] All' label in
the frontend.
- 2 bundles: a base '18.0' (is_base, sticky=True via compute) and a
feature-like branch (surfaces under the 'nosticky' filter).
- 4 commits with invented authors and SHAs.
- 4 build.params with unique commit_link_ids (distinct fingerprints).
- 4 builds covering done/ok, done/ko, running and pending states.
- 4 batches: 2 on the base bundle + 2 on the feature bundle so each
build sits in its own batch (matches real runbot: every new commit
on a PR opens a fresh batch).
- 4 batch.slot records wiring batches -> builds (frontend traversal,
drives the per-trigger grouping in the bundle view).
- last_batch on both bundles patched via <function> after creation
(chicken-and-egg: bundle exists before its batch). The feature
bundle's last_batch is the newer one containing the pending build.
The pending build is intentionally left without host, so its
vscode_url stays False and the 'Open VS Code' button stays hidden
— matching the real runbot state machine and the guard already
covered by test_action_open_vscode_blocks_when_url_missing.
Change note:
Suma demo data en runbot_ux para inspeccionar las vistas (backend
form y frontend bundle/build) sin tener un runbot real montado.
La estructura es la minima que reproduce un arbol completo: 1
version, 1 repo + remote inerte (mode=disabled), 1 trigger 'All',
2 bundles (base + feature), 4 commits con autores inventados, 4
build.params, 4 builds con estados variados (done/ok, done/ko,
running, pending) repartidos en 4 batches (uno por build, como en
runbot real) y los batch.slot que conectan todo. Util tambien para
futuras iteraciones de runbot_ux.
…u (task #67684)
Frontend: the 'Open VS Code' entry now lives inside the build action
menu (the cog dropdown, runbot.build_menu) right after Rebuild,
instead of being an icon next to Sign in / Wake up in the action
bar. Visibility matches Rebuild's own condition
(global_state in ['done', 'running']) plus our vscode_url guard,
so a pending build (no host yet) does not surface the option.
The inherit is written as <record model='ir.ui.view'> with type=qweb
explicitly. The short-form <template inherit_id='...'> in this case
ate the parent's children at render time (Rebuild, Kill, etc.
vanished and only our injected element survived) even though the
xpath itself matched correctly when validated standalone with lxml.
Likely a quirk in how the short-form is converted internally when
the parent template has multiple root elements (button + div), but
not chased down further: the <record> form just works.
The xpath anchor is //a[i[hasclass('fa-refresh')]] — the fa-refresh
icon is exclusive to Rebuild within build_menu (Force Build uses
fa-level-up). The obvious anchor by @title was rejected: title is
on Odoo's TRANSLATED_ATTRS blocklist for view inheritance selectors.
Backend: drop the read-only <field name='vscode_url' widget='url'/>
from the build form. Visual noise next to the URL already exposed
by the 'Open VS Code' button.
Change note:
Mueve la entrada 'Open VS Code' del frontend al menu de acciones
(la ruedita) del build, justo despues de Rebuild. Visible con la
misma condicion que Rebuild (build done o running) mas el guard
de vscode_url (necesita host). En el form del backend saca el
campo vscode_url que estaba al lado del boton y no aportaba. La
herencia del template tuvo que escribirse como <record model=
'ir.ui.view'> explicito porque la sintaxis corta <template
inherit_id='...'> en este caso reemplazaba el contenido del padre
en lugar de extenderlo.
…task #67684) The 'Open VS Code' entry already hides when the build is pending (no host yet). It now also hides when the build's Dockerfile does not include our code-server reference layer, since in that case the container does not have code-server baked in and the URL would resolve to nothing. vscode_url: - becomes stored. The compute fires once when dest and host are set (= build pickup) and the resulting value is frozen afterwards. The Dockerfile state is read at that moment and later mutations to the layer set do NOT flip the field. This matches the physical reality: once a build's container is baked, adding or removing the reference layer on the Dockerfile cannot change the running container's contents — so the URL availability should not change either. - @api.depends stays on dest + host only; no chain into params_id.dockerfile_id.layer_ids, since that would defeat the freeze. - env.ref's runbot_attach_vscode.docker_layer_code_server with raise_if_not_found=False (defensive — should always be there as long as the module is installed, but the check is cheap). - iterates the build's dockerfile layers and matches any layer of type reference_layer pointing at the source layer. - only sets the URL when dest, host and the layer are all present at compute time. Test setup is extended so the URL tests have a real Dockerfile + reference layer + params chain. A new test covers the negative path (dest + host but no layer -> False) so the layer guard does not silently rot. Change note: Ahora la opcion 'Open VS Code' tambien queda oculta si el Dockerfile del build no incluye nuestra capa de code-server. La condicion compuesta queda: dest + host + reference_layer hacia runbot_attach_vscode.docker_layer_code_server presente en el Dockerfile al momento del primer compute. El campo es stored y solo depende de dest/host: una vez evaluado al asignarse el host (= build pickup), mutaciones posteriores al layer set del Dockerfile no flipean el campo. Tests actualizados con un Dockerfile + capa de referencia + params reales; un test nuevo cubre el caso 'sin capa -> vscode_url False'.
…ult (task #67684) Pairs with the previous commit that hides 'Open VS Code' when the build's Dockerfile lacks our code-server reference layer. Without this update the demo would never surface the option: the demo build.params use runbot.docker_default, which by itself carries no reference to the source layer. The demo file (renamed runbot_attach_vscode_demo.xml) attaches the code-server reference layer to runbot.docker_default. noupdate=1 so the admin can delete the record without it re-appearing on update. Because the file lives in /demo/ it only loads when demo data is requested, so a production install (without demo) does not opt every build into code-server automatically. vscode_url is stored and its @api.depends is narrow (dest, host) by design: admin-time mutations on the Dockerfile must not flip the field on already-built builds. The flip side is that the reference layer added by this demo would not propagate to builds created before the demo file loaded (typical case: runbot_ux's demo loads first and creates its builds against a layer-less docker_default). The demo file therefore ends with a <function> call to runbot.build._compute_vscode_url on every existing build, which nudges the stored field once, right after the reference layer is in place. No cross-module dependency: runbot_attach_vscode still depends only on runbot. runbot_ux's demo no longer creates a dedicated Dockerfile; its build.params use the default Dockerfile so the reference layer added here actually applies to them. Change note: La demo del modulo adjunta code-server al Dockerfile default de runbot (runbot.docker_default) mediante una reference layer, de modo que cualquier build del demo la herede automaticamente. Al final del archivo de demo una llamada <function> fuerza el recompute de vscode_url sobre los builds existentes, para que los ya creados antes del demo (caso tipico: runbot_ux cargando antes) tomen el layer recien agregado. Sin dependencia cruzada: runbot_attach_vscode sigue dependiendo solo de runbot, y runbot_ux saca el Dockerfile propio del demo para usar el default.
End-to-end runtime wiring so the 'Open VS Code' entry resolves to a live code-server session inside the build container. Lazy start by design: code-server is launched on first click via docker exec, not at every wake-up, so the cost is paid only for builds where someone actually attaches. Pieces: - models/runbot_build_config.py (new): inherit runbot.build.config.step._run_run_odoo. When the build's Dockerfile carries the code-server reference layer, append build.port + 2 to exposed_ports so Docker maps container port 8071 to that host port at wake-up time. The port mapping has to be declared at docker run time (Docker can't add port mappings to a running container later) but code-server itself is NOT started here. - models/runbot_build.py: extend action_open_vscode. After the existing internal-user / vscode_url guards, ensure the build container is in RUNNING state, then docker exec (detached) into it with a shell guard 'pgrep -x code-server >/dev/null || exec code-server ...'. Idempotent: a second click does nothing if code-server is already up. After kicking off the exec, poll the host port for up to 5s so the browser does not race against the bind. Refactored the layer detection out of _compute_vscode_url into a public _has_vscode_layer() helper used by both the compute and the run-step inherit. - views/runbot_nginx.xml (new): QWeb inherit of runbot.nginx_config. Injects a per-build server block at the server_build_anchor that routes <dest>-vscode.<host> to 127.0.0.1:<build.port + 2>, with the WebSocket headers code-server needs. Placed before the generic <dest>(-suffix)?.<host> block so the more specific vscode regex matches first. README updated: Phase 1 + Phase 2 marked as shipped, Phase 3 (real auth token, writable scratch dir) noted as pending. Change note: Suma el cableado runtime para que 'Open VS Code' efectivamente abra un code-server vivo adentro del container del build. Lazy start: el code-server arranca recien cuando el usuario clickea el boton (via docker exec idempotente), no en cada wake-up. Asi los run steps automaticos de las configs CI no pagan los ~150 MB extra cuando nadie va a attachear. El mapeo de puerto (container 8071 -> host build.port + 2) si se reserva en el docker run porque Docker no permite agregar mapeos a un container ya corriendo. Nginx routea <dest>-vscode.<host> al puerto via un server block injectado por QWeb inherit del runbot.nginx_config.
…layer (task #67684)
…layer (task #67684)
…ion menu (task #67684)
…layer (task #67684)
…task #67684) Sumo Node 20 (NodeSource) + Claude Code, OpenAI Codex, Google Gemini y OpenCode al `runbot.docker_layer` template `code_server`. Todo en un solo RUN para mantener bajo el conteo de image layers. Costo en imagen: +~1.1 GB de /usr/lib/node_modules (encima de los 430 MB de code-server). Resuelve el acceptance criteria "CLIs disponibles: Claude Code, Codex CLI, Gemini CLI, OpenCode. Bake-in en imagen, no install runtime" de la spec imagen-cli-vscode-server-attacheable.
…cookie + nginx auth_request (task #67684) Cierra el agujero de que cualquier URL `<dest>-vscode.<host>` adivinada entraba al editor sin pasar por Odoo. Approach (Opción C — ver D9 en la spec imagen-cli-vscode-server-attacheable): - Al clickear "Open VS Code", la route `/runbot/vscode/<id>` valida usuario interno + container running, emite un token HMAC-SHA256 sobre `database.secret` con payload `build_id|user_id|exp` (TTL 4h), lo setea como cookie `vscode_token` con `Domain=<build.host>` (cubre el subdominio por RFC 6265), HttpOnly + Secure + SameSite=Lax, y redirige al `vscode_url`. - Nueva route `auth='public'` `/runbot/vscode/auth_check?build=<id>` que valida el cookie HMAC + expiry + build_id. La consume nginx vía `auth_request` antes de proxiar cada request al subdominio. - El bloque nginx interno per-build agrega `auth_request /__vscode_auth_check` y `error_page 401 403 = @vscode_login_redirect` que rebota a `/runbot/vscode/<id>` (que re-emite cookie si hay sesión, o tira a /web/login si no). code-server sigue corriendo `--auth none`: la auth está toda en la capa nginx. - El botón backend `action_open_vscode` ahora redirige a la route HTTP (no a `vscode_url` directo) porque una action que devuelve ir.actions.act_url no puede setear cookies en la response. Aprovecho para bajar al XML mejoras al bloque nginx que estaban solo en DB: Connection condicional upgrade/close (en vez de hardcoded "Upgrade"), `proxy_read_timeout 7d` + `proxy_send_timeout 7d`, y saco los headers `X-Forwarded-For 127.0.0.42` espurios. Cambio infra acompañante (one-off, fuera del módulo): el wildcard `*.runbot.dev-adhoc.com` en /etc/nginx/sites-available/nginx.conf necesita forwardear Upgrade/Connection en location /. Documentado en mensaje interno de la task.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
runbot_attach_vscodecon unrunbot.docker_layerreusable (xml_idrunbot_attach_vscode.docker_layer_code_server) que instala code-server sobre cualquier Dockerfile del runbot.reference_layerque apunta al xml_id.ingadhoc/adhoc-way→specs/10_draft/imagen-cli-vscode-server-attacheable.md.Scope de este PR
runbot.build/runbot.build_configtodavía.adhoc_odoo_19).Follow-up (mismo PR, próximos commits)
runbot.buildconaction_open_vscode(docker execpost-wake → arranca code-server en0.0.0.0:8080con--auth nonepara spike)._get_vscode_url(build)retornandovscode-{dest}-{db_suffix}.{host}.views/build_views.xml.network_mode='none'en build_config (riesgo principal flagged por la spec).Test plan
odoo -d 18.0-runbot-67684 -i runbot_attach_vscode --stop-after-init --without-demo=all --no-http) — 51 módulos cargados OK, record con xml_id creado.reference_layeral Dockerfileadhoc_odoo_19y rebuild de la imagen pasa.docker run --rm odoo:adhoc_odoo_19 code-server --versionresponde (validación de bake-in).