mpk: key refs resolve through the ukrrs/mopac-keyproxy hop ([keyproxy]
url + token_ref, POST /v1/resolve, short in-memory cache); env:/file:/
literal: refs keep working untouched, so the harness runs with or
without keyproxy up. [loop] gains poll_interval_secs + state_dir for the
self-host daemon; [redmine.status_map] (quoted TOML keys) drives status
transitions; [gitea] carries the optional REPORT-commit step, off by
default. All hosts live in harness.toml, none in code.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
153 lines
4.4 KiB
Go
153 lines
4.4 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
// fakeKeyProxy mimics ukrrs/mopac-keyproxy POST /v1/resolve: bearer auth,
|
|
// {"ref":"mpk-<name>"} in, {"value":...} out.
|
|
type fakeKeyProxy struct {
|
|
t *testing.T
|
|
token string
|
|
resolve func(ref string) (string, int)
|
|
hits atomic.Int64
|
|
srv *httptest.Server
|
|
}
|
|
|
|
func newFakeKeyProxy(t *testing.T, token string) *fakeKeyProxy {
|
|
t.Helper()
|
|
f := &fakeKeyProxy{t: t, token: token}
|
|
f.resolve = func(ref string) (string, int) { return "value-for-" + ref, 200 }
|
|
f.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/v1/resolve" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if got := r.Header.Get("Authorization"); got != "Bearer "+f.token {
|
|
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(io.LimitReader(r.Body, 1<<20))
|
|
var body struct {
|
|
Ref string `json:"ref"`
|
|
}
|
|
if err := json.Unmarshal(raw, &body); err != nil || body.Ref == "" {
|
|
http.Error(w, `{"error":"bad_request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
f.hits.Add(1)
|
|
value, code := f.resolve(body.Ref)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
w.Write([]byte(`{"value":"` + value + `"}`))
|
|
}))
|
|
t.Cleanup(f.srv.Close)
|
|
return f
|
|
}
|
|
|
|
func TestKeyResolverMPK(t *testing.T) {
|
|
kp := newFakeKeyProxy(t, "kp-token")
|
|
cfg := Default()
|
|
cfg.KeyProxy.URL = kp.srv.URL
|
|
cfg.KeyProxy.TokenRef = "literal:kp-token"
|
|
r := NewKeyResolver(cfg)
|
|
|
|
// Auto-prefix: mpk:redmine -> ref "mpk-redmine" on the wire.
|
|
v, err := r.Resolve(context.Background(), "mpk:redmine")
|
|
if err != nil {
|
|
t.Fatalf("Resolve: %v", err)
|
|
}
|
|
if v != "value-for-mpk-redmine" {
|
|
t.Errorf("value = %q", v)
|
|
}
|
|
// Explicit full form resolves identically (cache hit, no new call).
|
|
v, err = r.Resolve(context.Background(), "mpk:mpk-redmine")
|
|
if err != nil || v != "value-for-mpk-redmine" {
|
|
t.Errorf("full form = %q err=%v", v, err)
|
|
}
|
|
if n := kp.hits.Load(); n != 1 {
|
|
t.Errorf("keyproxy hits = %d, want 1 (cache must absorb the repeat)", n)
|
|
}
|
|
// A different ref is a fresh call.
|
|
if _, err := r.Resolve(context.Background(), "mpk:litellm"); err != nil {
|
|
t.Fatalf("Resolve litellm: %v", err)
|
|
}
|
|
if n := kp.hits.Load(); n != 2 {
|
|
t.Errorf("keyproxy hits = %d, want 2", n)
|
|
}
|
|
}
|
|
|
|
func TestKeyResolverLocalRefsStillWork(t *testing.T) {
|
|
kp := newFakeKeyProxy(t, "kp-token")
|
|
cfg := Default()
|
|
cfg.KeyProxy.URL = kp.srv.URL
|
|
cfg.KeyProxy.TokenRef = "literal:kp-token"
|
|
r := NewKeyResolver(cfg)
|
|
|
|
t.Setenv("HARNESS_KP_TEST", "envval")
|
|
for ref, want := range map[string]string{
|
|
"env:HARNESS_KP_TEST": "envval",
|
|
"literal:abc": "abc",
|
|
} {
|
|
if v, err := r.Resolve(context.Background(), ref); err != nil || v != want {
|
|
t.Errorf("Resolve(%q) = %q err=%v, want %q", ref, v, err, want)
|
|
}
|
|
}
|
|
if n := kp.hits.Load(); n != 0 {
|
|
t.Errorf("local refs must not touch keyproxy, hits = %d", n)
|
|
}
|
|
}
|
|
|
|
func TestKeyResolverErrors(t *testing.T) {
|
|
kp := newFakeKeyProxy(t, "kp-token")
|
|
|
|
// No [keyproxy] url: clear error pointing at the config.
|
|
cfg := Default()
|
|
r := NewKeyResolver(cfg)
|
|
_, err := r.Resolve(context.Background(), "mpk:redmine")
|
|
if err == nil || !strings.Contains(err.Error(), "no [keyproxy] url") {
|
|
t.Errorf("no-url error = %v", err)
|
|
}
|
|
|
|
// Bad token: 401 surfaces, the presented token never does.
|
|
cfg = Default()
|
|
cfg.KeyProxy.URL = kp.srv.URL
|
|
cfg.KeyProxy.TokenRef = "literal:wrong-token"
|
|
r = NewKeyResolver(cfg)
|
|
_, err = r.Resolve(context.Background(), "mpk:redmine")
|
|
if err == nil || !strings.Contains(err.Error(), "401") {
|
|
t.Errorf("401 error = %v", err)
|
|
}
|
|
if strings.Contains(err.Error(), "wrong-token") {
|
|
t.Errorf("error leaks token: %v", err)
|
|
}
|
|
|
|
// Unknown ref: 404 with fixed body.
|
|
cfg = Default()
|
|
cfg.KeyProxy.URL = kp.srv.URL
|
|
cfg.KeyProxy.TokenRef = "literal:kp-token"
|
|
kp.resolve = func(ref string) (string, int) { return "", 404 }
|
|
r = NewKeyResolver(cfg)
|
|
_, err = r.Resolve(context.Background(), "mpk:nope")
|
|
if err == nil || !strings.Contains(err.Error(), "404") {
|
|
t.Errorf("404 error = %v", err)
|
|
}
|
|
|
|
// Hop down: transport error, not a hang or panic.
|
|
cfg = Default()
|
|
cfg.KeyProxy.URL = "http://127.0.0.1:1"
|
|
cfg.KeyProxy.TokenRef = "literal:kp-token"
|
|
r = NewKeyResolver(cfg)
|
|
_, err = r.Resolve(context.Background(), "mpk:redmine")
|
|
if err == nil {
|
|
t.Errorf("dead hop should error")
|
|
}
|
|
}
|