--- name: gsc_auth kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def gsc_auth(credentials_path: str = \"\", subject: str = \"\") -> object" description: "Autentica contra la Google Search Console API v1 (searchconsole/webmasters) con una cuenta de servicio JSON. Lee el JSON de credentials_path o, si esta vacio, de la env var GSC_SA_JSON; lanza ValueError claro si falta. Usa service_account.Credentials.from_service_account_file con scope https://www.googleapis.com/auth/webmasters.readonly (solo lectura). subject opcional aplica with_subject(subject) para domain-wide delegation (normalmente vacio en GSC). Construye y retorna el objeto service de googleapiclient.discovery.build('searchconsole','v1', cache_discovery=False) listo para consumir por pull_gsc_search_analytics. Requiere google-api-python-client y google-auth." tags: [seo, gsc, infra, google, search-console, auth, service-account] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [os, google.oauth2.service_account, googleapiclient.discovery] params: - name: credentials_path desc: "ruta al JSON de la service account de GCP. Si esta vacio, se lee de la env var GSC_SA_JSON. Si tampoco existe, se lanza ValueError. El JSON es un secreto: resolver desde pass o una ruta fuera del repo, nunca commitear." - name: subject desc: "opcional. Email para domain-wide delegation (impersonation) via with_subject. Normalmente vacio en Search Console, donde la SA se anade directamente como usuario en GSC sin requerir delegation." output: "object. El service de googleapiclient (googleapiclient.discovery.Resource) para la API 'searchconsole' v1, autenticado con scope webmasters.readonly y cache_discovery=False. Se pasa a funciones consumidoras como pull_gsc_search_analytics." tested: true tests: - "test_build_se_llama_con_searchconsole_v1_y_cache_off" - "test_credentials_se_cargan_con_scope_readonly" - "test_fallback_a_env_var_gsc_sa_json" - "test_subject_aplica_with_subject" - "test_error_cuando_falta_credential" test_file_path: "python/functions/infra/gsc_auth_test.py" file_path: "python/functions/infra/gsc_auth.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") from infra import gsc_auth # Opcion A: ruta explicita al JSON de la service account service = gsc_auth(credentials_path="/home/enmanuel/secrets/gsc-sa.json") # Opcion B: leer la ruta de la env var GSC_SA_JSON # export GSC_SA_JSON=/home/enmanuel/secrets/gsc-sa.json service = gsc_auth() # Verificar la autenticacion listando los sitios verificados: sites = service.sites().list().execute() print([s["siteUrl"] for s in sites.get("siteEntry", [])]) ``` ## Cuando usarla Antes de `pull_gsc_search_analytics` (o cualquier llamada a la Search Console API): la usas para obtener el objeto `service` autenticado una sola vez y reutilizarlo en las consultas posteriores (Search Analytics, sitemaps, sites). Es el punto de entrada del capability group `seo`. ## Gotchas - **Impura**: lee un JSON del disco y construye un cliente HTTP de Google. No es determinista ni componible en el nucleo puro. - **Dar de alta la SA en Search Console**: el email de la service account debe anadirse manualmente como usuario en Search Console > Settings > Users and permissions (rol Restricted/Full). Sin esto la auth funciona pero las consultas devuelven 403 / sitios vacios. - **Habilitar la API**: la "Search Console API" debe estar habilitada en el proyecto GCP de la service account (consola de APIs & Services). Si no, el primer `.execute()` falla con un error de API deshabilitada. - **El JSON de la SA es un secreto**: no commitear nunca. Guardarlo en `pass` o en una ruta fuera del repo y pasar la ruta por `credentials_path` o la env var `GSC_SA_JSON`. - **`subject` casi siempre vacio**: domain-wide delegation solo aplica si impersonas a un usuario de un dominio Workspace; en GSC lo normal es anadir la SA directamente como usuario y dejar `subject=""`. - **Dependencias**: requiere `google-api-python-client` y `google-auth` en el venv. Ya estan en `python/pyproject.toml`. ## Capability growth log (sin cambios — v1.0.0 inicial)