mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
193 lines
6.7 KiB
Go
193 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/provider"
|
|
"git.novox.be/novox/mesh-sdk/go/provider/harness"
|
|
)
|
|
|
|
const testIssuer = "https://id.example.org/realms/Novox"
|
|
|
|
// keycloak's own part of the moved path (issue 849): the shared driving of it is proven in the SDK's harness package.
|
|
|
|
// What a consumer is told is what it contributes, with the names the controller composed for it over them (issue 792).
|
|
func TestAConsumersValuesAreWhatItContributesWithItsComposedNames(t *testing.T) {
|
|
got := valuesOf(provider.Entry{Contributes: map[string]any{"label": "x", "name": "old"},
|
|
Names: map[string]string{"name": "x.example.org"}})
|
|
if want := map[string]any{"label": "x", "name": "x.example.org"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("%v, want %v", got, want)
|
|
}
|
|
if served := servedValues(testIssuer); served["issuer"] != testIssuer {
|
|
t.Fatalf("%v", served)
|
|
}
|
|
}
|
|
|
|
// Where the file is not there, keycloak takes the grant directory, as before; where it is, the helper.
|
|
func TestTheGrantPathIsTakenWhereTheConsumersFileIsAbsent(t *testing.T) {
|
|
dir := t.TempDir()
|
|
at := filepath.Join(dir, "consumers.json")
|
|
env := func(k string) string {
|
|
if k == "MESH_CONSUMERS_FILE" {
|
|
return at
|
|
}
|
|
return ""
|
|
}
|
|
if got := handedConsumersFile(env, os.Stat); got != "" {
|
|
t.Fatalf("took %q for a file that is not there", got)
|
|
}
|
|
if got := handedConsumersFile(func(string) string { return "" }, os.Stat); got != "" {
|
|
t.Fatalf("took %q with no environment", got)
|
|
}
|
|
if err := os.WriteFile(at, []byte("[]"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := handedConsumersFile(env, os.Stat); got != at {
|
|
t.Fatalf("took %q, want %q", got, at)
|
|
}
|
|
}
|
|
|
|
// The grant path restarts itself when the consumers file is first filled.
|
|
func TestTheGrantPathLeavesWhenTheConsumersFileIsFilled(t *testing.T) {
|
|
at := filepath.Join(t.TempDir(), "consumers.json")
|
|
done := make(chan struct{})
|
|
go exitWhenFilled(at, os.Stat, time.Millisecond, func() { close(done) })
|
|
select {
|
|
case <-done:
|
|
t.Fatal("left before the file was there")
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
if err := os.WriteFile(at, []byte("[]"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("did not leave when the file was filled")
|
|
}
|
|
}
|
|
|
|
// bucketBus is the helper's bus over in-memory buckets.
|
|
type bucketBus struct {
|
|
mu sync.Mutex
|
|
buckets map[string]map[string][]byte
|
|
}
|
|
|
|
func (b *bucketBus) Watch(_ context.Context, bucket string, handle func(string, []byte)) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
for k, v := range b.buckets[bucket] {
|
|
handle(k, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *bucketBus) Put(_ context.Context, bucket, key string, value []byte) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if b.buckets[bucket] == nil {
|
|
b.buckets[bucket] = map[string][]byte{}
|
|
}
|
|
b.buckets[bucket][key] = append([]byte(nil), value...)
|
|
return nil
|
|
}
|
|
|
|
func (b *bucketBus) Delete(_ context.Context, bucket, key string) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
delete(b.buckets[bucket], key)
|
|
return nil
|
|
}
|
|
|
|
// grafanaContributes and grafanaNames are what grafana's plan entry carries for keycloak: its contribution, and the
|
|
// names the controller composed for it (issues 776, 792).
|
|
var grafanaContributes = map[string]any{"label": "grafana", "endpoint": "web", "callback": "/login/generic_oauth"}
|
|
var grafanaNames = map[string]string{"name": "grafana.example.org", "internal-name": "grafana.home.internal"}
|
|
|
|
// Against a Keycloak, both paths leave the same client: the grant file through the harness, and the consumers' file
|
|
// through the SDK's moved path with keycloak's spec (issue 849).
|
|
func TestBothPathsLeaveTheSameClientInKeycloak(t *testing.T) {
|
|
grafana := provider.ConsumerIdentity("home", "grafana")
|
|
values := map[string]any{"name": grafanaNames["name"], "internal-name": grafanaNames["internal-name"]}
|
|
for k, v := range grafanaContributes {
|
|
values[k] = v
|
|
}
|
|
now := time.Date(2026, 10, 15, 12, 0, 0, 0, time.UTC)
|
|
made := func(through func(p provisioner)) Rep {
|
|
f, o := oidcWorld(t)
|
|
through(provisioner{clients: o, announce: func(string, map[string]any) {}, log: func(string, ...any) {}})
|
|
return only(t, f, grafana)
|
|
}
|
|
viaGrant := made(func(p provisioner) {
|
|
dir := t.TempDir()
|
|
secret := filepath.Join(dir, grafana+".secret")
|
|
if err := os.WriteFile(secret, []byte("pw-"+grafana+"\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(map[string]any{"requirement": ProvisionName, "given": []map[string]any{
|
|
{"as": grafana, "node": "home", "values": values, "secret": secret}}})
|
|
receives := filepath.Join(dir, "mesh.json")
|
|
if err := os.WriteFile(receives, raw, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h := &harness.Harness{Resource: ProvisionName, Receives: receives, Adapter: p,
|
|
Now: func() time.Time { return now }, Log: func(string, ...any) {}}
|
|
h.Reconcile(ctx)
|
|
})
|
|
viaFile := made(func(p provisioner) {
|
|
list, _ := json.Marshal([]provider.ConsumerCredential{{Consumer: "home", Module: "grafana",
|
|
Credential: provider.PairName("novox", ProvisionName, "home", "grafana", ""), Value: "pw-" + grafana}})
|
|
file := filepath.Join(t.TempDir(), "consumers.json")
|
|
if err := os.WriteFile(file, list, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h := &harness.Harness{Resource: ProvisionName, Adapter: p, Node: "novox",
|
|
Now: func() time.Time { return now }, Log: func(string, ...any) {}}
|
|
run, _, _, err := moved(file, h, testIssuer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bus := &bucketBus{buckets: map[string]map[string][]byte{}}
|
|
seatPlan(bus, "home", "grafana", map[string]any{
|
|
"provisions": []any{map[string]any{"provision": ProvisionName, "provider": "novox",
|
|
"provider-module": "keycloak", "contributes": grafanaContributes, "names": grafanaNames}}})
|
|
pr := provider.New(provider.Options{Node: "novox", Module: "keycloak", Bus: bus,
|
|
Vault: provider.ConsumersFile{Paths: []string{file}},
|
|
Offers: map[string]provider.Offer{ProvisionName: {Credential: provider.PerConsumer,
|
|
Serves: servedValues(testIssuer)}},
|
|
Make: run.Make, Gone: run.Gone, Logf: func(string, ...any) {}})
|
|
out, err := pr.Pass(ctx)
|
|
if err != nil || len(out) != 1 || out[0].Status != provider.Answered {
|
|
t.Fatalf("%+v %v", out, err)
|
|
}
|
|
})
|
|
// Keycloak's own ids differ between two runs; nothing the mesh sets does.
|
|
for _, c := range []Rep{viaGrant, viaFile} {
|
|
delete(c, "id")
|
|
raw, _ := json.Marshal(c)
|
|
var again Rep
|
|
_ = json.Unmarshal(raw, &again)
|
|
for k := range c {
|
|
delete(c, k)
|
|
}
|
|
for k, v := range again {
|
|
c[k] = v
|
|
}
|
|
if mappers, ok := c["protocolMappers"].([]any); ok {
|
|
for _, m := range mappers {
|
|
delete(m.(map[string]any), "id")
|
|
}
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(viaGrant, viaFile) {
|
|
t.Fatalf("through the file %v, through the grant %v", viaFile, viaGrant)
|
|
}
|
|
}
|