26cd05e961
agentpr always read gitea/creds/unkin-agent from a bare const, so a service like repospawner could not run it as its own Gitea identity. - Replace the GiteaCredsPath const with a function: GITEA_CREDS_PATH when set, otherwise gitea/creds/<AGENT_LOGIN>. Unset env still resolves to gitea/creds/unkin-agent, so existing callers are unchanged. - Thread the creds path through fetchGiteaToken/readGiteaCreds instead of reading a package-level const, and report it in the error messages. - Make agentpr's help text login-agnostic and document both variables.
156 lines
5.1 KiB
Go
156 lines
5.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestGiteaCredsPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
agentLogin string
|
|
credsPath string
|
|
want string
|
|
}{
|
|
{"no env keeps the historical path", "", "", "gitea/creds/unkin-agent"},
|
|
{"derived from AGENT_LOGIN", "repospawner", "", "gitea/creds/repospawner"},
|
|
{"GITEA_CREDS_PATH beats AGENT_LOGIN", "repospawner", "gitea/creds/someone-else", "gitea/creds/someone-else"},
|
|
{"GITEA_CREDS_PATH beats the default", "", "other-gitea/creds/bot", "other-gitea/creds/bot"},
|
|
{"override is trimmed for joining onto /v1/", "", " /gitea/creds/bot/ ", "gitea/creds/bot"},
|
|
{"blank override falls back to the login", "repospawner", " ", "gitea/creds/repospawner"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("AGENT_LOGIN", tt.agentLogin)
|
|
t.Setenv("GITEA_CREDS_PATH", tt.credsPath)
|
|
if got := GiteaCredsPath(); got != tt.want {
|
|
t.Errorf("GiteaCredsPath() = %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// recordingVault serves the AppRole login plus any creds path under /v1/,
|
|
// recording which one was read so tests can assert the selected path.
|
|
type recordingVault struct {
|
|
mu sync.Mutex
|
|
credsPath string
|
|
}
|
|
|
|
func (v *recordingVault) path() string {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
return v.credsPath
|
|
}
|
|
|
|
// fakeEstate serves both Vault (/v1/...) and Gitea (/api/v1/...) from one
|
|
// server, so a test can drive the whole token-then-API flow.
|
|
func fakeEstate(t *testing.T, giteaToken, login string) (*httptest.Server, *recordingVault) {
|
|
t.Helper()
|
|
rec := &recordingVault{}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/v1/auth/approle/login", func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = io.WriteString(w, `{"auth":{"client_token":"s.vaulttoken"}}`)
|
|
})
|
|
mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) {
|
|
rec.mu.Lock()
|
|
rec.credsPath = strings.TrimPrefix(r.URL.Path, "/v1/")
|
|
rec.mu.Unlock()
|
|
_, _ = io.WriteString(w, `{"data":{"token":"`+giteaToken+`"}}`)
|
|
})
|
|
mux.HandleFunc("/api/v1/user", func(w http.ResponseWriter, r *http.Request) {
|
|
if got := r.Header.Get("Authorization"); got != "token "+giteaToken {
|
|
t.Errorf("whoami auth header = %q, want token %s", got, giteaToken)
|
|
}
|
|
_, _ = io.WriteString(w, `{"login":"`+login+`","id":7}`)
|
|
})
|
|
mux.HandleFunc("/api/v1/repos/unkin/repo/pulls", func(w http.ResponseWriter, r *http.Request) {
|
|
if got := r.Header.Get("Authorization"); got != "token "+giteaToken {
|
|
t.Errorf("create PR auth header = %q, want token %s", got, giteaToken)
|
|
}
|
|
var body CreatePROptions
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
_, _ = io.WriteString(w, `{"number":12,"state":"open","html_url":"`+r.Host+`/pulls/12"}`)
|
|
})
|
|
return httptest.NewServer(mux), rec
|
|
}
|
|
|
|
// A service that sets AGENT_LOGIN must read its own creds path and act as its
|
|
// own Gitea identity for both whoami and PR creation.
|
|
func TestWhoamiAndPRUseSelectedCredsPath(t *testing.T) {
|
|
srv, rec := fakeEstate(t, "gitea-repospawner", "repospawner")
|
|
defer srv.Close()
|
|
|
|
t.Setenv("AGENT_LOGIN", "repospawner")
|
|
t.Setenv("GITEA_CREDS_PATH", "")
|
|
t.Setenv("VAULT_ADDR", srv.URL)
|
|
t.Setenv("GITEA_URL", srv.URL)
|
|
|
|
tok, err := fetchGiteaToken(VaultAddr(), RoleID(), GiteaCredsPath())
|
|
if err != nil {
|
|
t.Fatalf("fetchGiteaToken: %v", err)
|
|
}
|
|
if got := rec.path(); got != "gitea/creds/repospawner" {
|
|
t.Errorf("vault read path = %q, want gitea/creds/repospawner", got)
|
|
}
|
|
if tok != "gitea-repospawner" {
|
|
t.Fatalf("token = %q, want gitea-repospawner", tok)
|
|
}
|
|
|
|
c := NewGiteaClient(tok)
|
|
u, err := c.Whoami()
|
|
if err != nil {
|
|
t.Fatalf("Whoami: %v", err)
|
|
}
|
|
if u.Login != "repospawner" {
|
|
t.Errorf("whoami login = %q, want repospawner", u.Login)
|
|
}
|
|
|
|
pr, err := c.CreatePR("unkin/repo", CreatePROptions{Base: "main", Head: "feature", Title: "T"})
|
|
if err != nil {
|
|
t.Fatalf("CreatePR: %v", err)
|
|
}
|
|
if pr.Number != 12 {
|
|
t.Errorf("PR number = %d, want 12", pr.Number)
|
|
}
|
|
}
|
|
|
|
// GITEA_CREDS_PATH must win even when AGENT_LOGIN names a different identity.
|
|
func TestCredsPathOverrideBeatsAgentLogin(t *testing.T) {
|
|
srv, rec := fakeEstate(t, "gitea-override", "someone-else")
|
|
defer srv.Close()
|
|
|
|
t.Setenv("AGENT_LOGIN", "repospawner")
|
|
t.Setenv("GITEA_CREDS_PATH", "gitea/creds/someone-else")
|
|
t.Setenv("VAULT_ADDR", srv.URL)
|
|
|
|
if _, err := fetchGiteaToken(VaultAddr(), RoleID(), GiteaCredsPath()); err != nil {
|
|
t.Fatalf("fetchGiteaToken: %v", err)
|
|
}
|
|
if got := rec.path(); got != "gitea/creds/someone-else" {
|
|
t.Errorf("vault read path = %q, want gitea/creds/someone-else", got)
|
|
}
|
|
}
|
|
|
|
// With no env set the tools must still read the exact path they always did.
|
|
func TestCredsPathDefaultIsBackwardCompatible(t *testing.T) {
|
|
srv, rec := fakeEstate(t, "gitea-abc", "unkin-agent")
|
|
defer srv.Close()
|
|
|
|
t.Setenv("AGENT_LOGIN", "")
|
|
t.Setenv("GITEA_CREDS_PATH", "")
|
|
t.Setenv("VAULT_ADDR", srv.URL)
|
|
|
|
if _, err := fetchGiteaToken(VaultAddr(), RoleID(), GiteaCredsPath()); err != nil {
|
|
t.Fatalf("fetchGiteaToken: %v", err)
|
|
}
|
|
if got := rec.path(); got != "gitea/creds/unkin-agent" {
|
|
t.Errorf("vault read path = %q, want gitea/creds/unkin-agent", got)
|
|
}
|
|
}
|