feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: hoppscotch_set_environment
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def hoppscotch_set_environment(team_id: str, name: str, variables: list[dict], *, access_token: str, backend_url: str = \"http://localhost:3170\") -> dict"
|
||||
description: "Crea o actualiza (idempotente por nombre) un Team Environment de Hoppscotch self-hosted via GraphQL, resolviendo secretos desde pass. Lista los environments de la team y, si ya existe uno con ese name, llama updateTeamEnvironment; si no, createTeamEnvironment. Cualquier variable cuyo value empiece por 'pass:' se resuelve con pass_get_secret y se fuerza secret=True. Los valores secretos nunca se logean ni aparecen en el output: resolved_secrets lista solo los keys. Las mutations estan protegidas por GqlAuthGuard: el JWT de sesion (de hoppscotch_login) viaja en la cookie access_token."
|
||||
tags: [hoppscotch, flow-replay, http, secret, infra]
|
||||
uses_functions: [pass_get_secret_py_infra]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [json, requests]
|
||||
params:
|
||||
- name: team_id
|
||||
desc: "ID de la team duena del environment."
|
||||
- name: name
|
||||
desc: "nombre del environment. La idempotencia es por este nombre dentro de la team: si ya existe uno con este name se actualiza, si no se crea."
|
||||
- name: variables
|
||||
desc: "lista de dicts {key: str, value: str, secret: bool}. Si un value empieza por 'pass:' el resto se resuelve como ruta de pass con pass_get_secret y el secreto resuelto se usa como value real, forzando secret=True. Campos secret ausentes se tratan como False."
|
||||
- name: access_token
|
||||
desc: "JWT de sesion (de hoppscotch_login). Viaja en la cookie access_token, NO en el header Authorization."
|
||||
- name: backend_url
|
||||
desc: "base del backend Hoppscotch sin barra final. El endpoint GraphQL es {backend_url}/graphql. Default http://localhost:3170."
|
||||
output: "dict. En exito: {status: 'ok', id: str, name: str, action: 'created'|'updated', resolved_secrets: list[str]} donde resolved_secrets son SOLO los keys resueltos desde pass (nunca valores). En error: {status: 'error', error: str} (resolucion pass fallida con el key afectado, GraphQL errors, HTTP no JSON, o fallo de transporte). Si una variable pass: no se resuelve, NO se crea/actualiza el environment."
|
||||
tested: true
|
||||
tests:
|
||||
- "test_crea_cuando_no_existe"
|
||||
- "test_actualiza_cuando_existe"
|
||||
- "test_resuelve_secreto_desde_pass"
|
||||
- "test_error_pass_no_llama_mutation"
|
||||
test_file_path: "python/functions/infra/hoppscotch_set_environment_test.py"
|
||||
file_path: "python/functions/infra/hoppscotch_set_environment.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, "python/functions")
|
||||
from infra.hoppscotch_login import hoppscotch_login
|
||||
from infra.hoppscotch_set_environment import hoppscotch_set_environment
|
||||
|
||||
token = hoppscotch_login("admin@example.com")["access_token"]
|
||||
|
||||
# Una variable normal + una resuelta desde pass (se marca secret=True sola).
|
||||
result = hoppscotch_set_environment(
|
||||
team_id="cmq8kn0v500030xls1nvminjy",
|
||||
name="registry",
|
||||
variables=[
|
||||
{"key": "base_url", "value": "https://api.example.com", "secret": False},
|
||||
{"key": "api_key", "value": "pass:apis/licenseplatedata"},
|
||||
],
|
||||
access_token=token,
|
||||
)
|
||||
print(result)
|
||||
# {"status": "ok", "id": "...", "name": "registry",
|
||||
# "action": "updated", "resolved_secrets": ["api_key"]}
|
||||
# El valor crudo de api_key NUNCA aparece en el output.
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando quieras definir o actualizar las variables de un workspace (team
|
||||
environment) Hoppscotch self-hosted desde el registry, con los secretos
|
||||
resueltos desde `pass` en vez de hardcodearlos. Util en el patron grabar->
|
||||
destilar->reproducir: tras destilar un flujo, dejas sus tokens/credenciales como
|
||||
variables `pass:` de un environment que el humano ve en la GUI, sin que el
|
||||
secreto pase por el codigo. Idempotente por nombre: vuelve a llamarla para
|
||||
actualizar sin duplicar. Primero obten el `access_token` con `hoppscotch_login`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Idempotente por nombre.** Busca un environment con ese `name` en la team: si
|
||||
existe lo actualiza, si no lo crea. Dos teams pueden tener environments con el
|
||||
mismo nombre sin colisionar (la busqueda es por team).
|
||||
- **`pass:` resuelve de pass y fuerza `secret=True`.** Si el `value` empieza por
|
||||
`pass:`, el resto es la ruta de pass; el secreto resuelto reemplaza al value y
|
||||
la variable queda marcada como secreta aunque pasaras `secret=False`.
|
||||
- **Nunca logea secretos.** Ni en stdout ni en el output: `resolved_secrets`
|
||||
contiene solo los KEYS resueltos desde pass, jamas los valores. El valor crudo
|
||||
no aparece en el dict de retorno.
|
||||
- **Falla en pass = no se toca el environment.** Si una variable `pass:` no se
|
||||
puede resolver, la funcion aborta con `{"status": "error"}` y el key afectado
|
||||
ANTES de cualquier mutation: no deja el environment a medias.
|
||||
- **El access_token va como cookie, no como header Authorization.** Las mutations
|
||||
estan protegidas por GqlAuthGuard que lee el JWT de la cookie `access_token`.
|
||||
- **El secreto viaja en claro al backend self-host local por GraphQL.** Hoppscotch
|
||||
recibe el valor resuelto en el campo `variables`. Es aceptable porque el backend
|
||||
de referencia es local; no apuntes esta funcion a un Hoppscotch remoto sin TLS.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
v1.0.0 — version inicial. Listado + create + update validados contra el self-host
|
||||
vivo el 11/06/2026 (createTeamEnvironment / updateTeamEnvironment / listado via
|
||||
team{ teamEnvironments }). Resolucion `pass:` via pass_get_secret_py_infra.
|
||||
Reference in New Issue
Block a user