// Package agent holds the plumbing shared by the agent-tools CLIs (agentpr and // watchpr): obtaining a Gitea token via Vault AppRole, talking to the Gitea // API, parsing PR references, and deciding when a watched PR changed // meaningfully. Both tools acquire their Gitea token the same way, so that // logic lives here once. package agent import ( "os" "sync" ) const ( // DefaultVaultAddr is the OpenBao/Vault address used when VAULT_ADDR is unset. DefaultVaultAddr = "https://vault.service.consul:8200" // DefaultRoleID is the agent AppRole role_id used when AGENT_APPROLE_ROLE_ID // is unset. Login uses role_id only (no secret_id). DefaultRoleID = "ababbcd3-9c77-5c6a-be2d-287fce9214a6" // GiteaCredsPath is the Vault path that mints a scoped Gitea token. GiteaCredsPath = "gitea/creds/unkin-agent" // DefaultGiteaURL is the Gitea base URL used when GITEA_URL is unset. DefaultGiteaURL = "https://git.unkin.net" // DefaultAgentLogin is the Gitea login of the agent whose own comments are // ignored by watchpr. Overridable via AGENT_LOGIN. DefaultAgentLogin = "unkin-agent" ) // VaultAddr returns the configured Vault address (env VAULT_ADDR or the default). func VaultAddr() string { if v := os.Getenv("VAULT_ADDR"); v != "" { return v } return DefaultVaultAddr } // RoleID returns the configured AppRole role_id (env AGENT_APPROLE_ROLE_ID or // the default). func RoleID() string { if v := os.Getenv("AGENT_APPROLE_ROLE_ID"); v != "" { return v } return DefaultRoleID } // GiteaURL returns the configured Gitea base URL (env GITEA_URL or the default). func GiteaURL() string { if v := os.Getenv("GITEA_URL"); v != "" { return v } return DefaultGiteaURL } // AgentLogin returns the login whose comments watchpr ignores (env AGENT_LOGIN // or the default). func AgentLogin() string { if v := os.Getenv("AGENT_LOGIN"); v != "" { return v } return DefaultAgentLogin } var ( tokenOnce sync.Once tokenValue string tokenErr error ) // GiteaToken returns a Gitea token, minting it via Vault AppRole on first call // and caching it in-process for the lifetime of the command. func GiteaToken() (string, error) { tokenOnce.Do(func() { tokenValue, tokenErr = fetchGiteaToken(VaultAddr(), RoleID()) }) return tokenValue, tokenErr } // fetchGiteaToken performs the AppRole login and reads the Gitea creds. It is // separated from GiteaToken so tests can exercise it directly against an // httptest server without touching the process-wide cache. func fetchGiteaToken(vaultAddr, roleID string) (string, error) { clientToken, err := approleLogin(vaultAddr, roleID) if err != nil { return "", err } return readGiteaCreds(vaultAddr, clientToken) }