Git¶
sbx.git es la API git del SDK de E2B sobre commands.run: cada método
construye la orden git con los argumentos de E2B y la ejecuta dentro del
sandbox como el usuario del sandbox. rayito-base trae git-core
(git-core-2.50.1-1.amzn2023.0.1, fijado en image/Dockerfile).
E2B marca este módulo como obsoleto
Rayito lo ofrece por paridad. Nada impide usar
sbx.commands.run("git ...") directamente.
from rayito import Sandbox
with Sandbox.create() as sbx:
sbx.git.clone("https://github.com/octo/demo.git", path="/home/user/demo", depth=1)
sbx.git.configure_user("Agente", "agente@example.com")
sbx.files.write("/home/user/demo/NOTAS.md", "hola\n")
sbx.git.add("/home/user/demo")
sbx.git.commit("/home/user/demo", "notas")
status = sbx.git.status("/home/user/demo")
print(status.current_branch, status.ahead, status.is_clean)
print(sbx.git.branches("/home/user/demo").branches)
import asyncio
import os
from rayito import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create() as sbx:
repo = "/home/user/demo"
await sbx.git.clone("https://github.com/octo/demo.git", path=repo, depth=1)
await sbx.git.create_branch(repo, "cambios")
await sbx.files.write(f"{repo}/NOTAS.md", "hola\n")
await sbx.git.add(repo)
await sbx.git.commit(repo, "notas", author_name="Agente", author_email="agente@example.com")
# token de vida corta y de un solo repo: el código del sandbox puede verlo
await sbx.git.push(repo, username="x-access-token", password=os.environ["GIT_TOKEN"])
print((await sbx.git.status(repo)).ahead)
asyncio.run(main())
import { Sandbox } from "rayito";
await using sbx = await Sandbox.create();
const repo = "/home/user/demo";
await sbx.git.clone("https://github.com/octo/demo.git", { path: repo, depth: 1 });
await sbx.git.configureUser("Agente", "agente@example.com");
await sbx.git.createBranch(repo, "cambios");
await sbx.files.write(`${repo}/NOTAS.md`, "hola\n");
await sbx.git.add(repo);
await sbx.git.commit(repo, "notas");
const status = await sbx.git.status(repo);
console.log(status.currentBranch, status.isClean, status.ahead);
await sbx.git.push(repo, { username: "x-access-token", password: process.env.GIT_TOKEN ?? "" });
console.log((await sbx.git.branches(repo)).branches);
Métodos (los mismos en Git y AsyncGit; en TypeScript en camelCase):
clone, init, remote_add, remote_get, status, branches,
create_branch, checkout_branch, delete_branch, add, commit,
reset, restore, push, pull, set_config, get_config,
dangerously_authenticate y configure_user. Todos aceptan además envs,
user, cwd, timeout y request_timeout, y fijan GIT_TERMINAL_PROMPT=0
(un git que pediría una contraseña falla en vez de colgarse). En TypeScript
los argumentos obligatorios siguen siendo posicionales (path, branch,
message, url en clone…) y los opcionales van en un objeto ({ path, depth },
{ username, password }, { authorName, authorEmail }…);
dangerouslyAuthenticate({ username, password, host }) recibe todo en el
objeto.
status() devuelve un GitStatus (current_branch, upstream, ahead,
behind, detached, file_status y las propiedades is_clean,
has_changes, has_staged, has_untracked); branches(), un
GitBranches (branches, current_branch); get_config() y
remote_get(), el valor o None. El resto devuelve el CommandResult de
la orden.
Credenciales¶
Trata el token como revelado al código del sandbox
Un username/password que pasas a clone, push o pull está al
alcance de cualquier código que ya haya corrido en ese sandbox con el
mismo usuario: puede haber dejado preparado su ~/.bash_profile (las
órdenes corren con un shell de login), su configuración de git o un
proceso que mire /proc. Usa sólo tokens de vida corta, de un solo
repositorio y con el mínimo permiso (por ejemplo, un token de
instalación de una GitHub App), nunca un token personal de larga
duración en un sandbox que ejecuta código no confiable. Es la misma
regla que para los secretos inyectados.
clone,pushypullconusername/password: la URL con credenciales está en.git/configmientras dura la orden. Tras uncloneel SDK deja el remotoorigincon la URL sin credenciales (salvodangerously_store_credentials=True); enpush/pullcambia la URL del remoto, ejecuta la orden y la restaura siempre, también si la orden falla o vence. Si la restauración no se puede hacer (el sandbox se pausa, se corta el stream), el loggerrayito.git(en TypeScript, elloggerdel sandbox) avisa sin la URL de que el token puede seguir en.git/config, y viajaría en un snapshot o enpersist=. UnpasswordsinusernamelanzaInvalidArgumentExceptionantes de ejecutar nada.- Endurecimiento: con credenciales, la orden de git corre sin hooks
(
-c core.hooksPath=/dev/null) y sin credential helpers (-c credential.helper=, para que un helper configurado no guarde el token en disco), y antes de enviar nada el SDK comprueba que la configuración de git no reescribe URLs (url.*.insteadOf); si lo hace, lanzaGitAuthExceptionsin haber enviado las credenciales. - Las credenciales viajan en la orden (
StartRequest.cmd) hastarayd, que nunca registra órdenes; el SDK tampoco las registra y las redacta (***) destdout,stderry del mensaje de unCommandExitException. dangerously_authenticate(username, password, host="github.com")guarda las credenciales congit credential approveen~/.git-credentialsdel usuario del sandbox: quedan visibles para el código del sandbox, igual que en E2B. Úsalo sólo con un token de ámbito mínimo y de vida corta (Seguridad).
Errores¶
| Situación | Python | TypeScript |
|---|---|---|
| el remoto pide credenciales | GitAuthException (nunca incluye la URL) |
GitAuthError |
la configuración de git reescribe URLs (url.*.insteadOf) y la llamada lleva credenciales |
GitAuthException, sin enviarlas |
GitAuthError |
push/pull sin rama de seguimiento |
GitUpstreamException con la orden que falta |
GitUpstreamError |
| cualquier otro fallo de git | CommandExitException (salida redactada si había credenciales) |
CommandExitError |
| argumentos inválidos | InvalidArgumentException, antes de ejecutar nada |
InvalidArgumentError |
En el shim de E2B, sbx.git es el mismo objeto y las excepciones tienen los
nombres de e2b.exceptions (GitAuthException, GitUpstreamException).