Skip to content
quarry Artifact [64b046b904]
Login

Artifact [64b046b904]

Artifact 64b046b904071261588aa0e8895f410e5e6303264b10e507564f9d71d8474228:


package main

import (
	"context"
	"errors"

	"craftdesign.group/quarry/internal/oltp"
)

// adminsTopic is the group every fleet-wide alert is addressed to: the
// error journal's first sighting of a fault, and a route that blows its
// render budget. It is a TOPIC, not a person. "admins" as a subscriber
// id names nobody, and notify answers a trigger for an unknown
// subscriber with a success — so an alert addressed that way is
// accepted, delivered to no one, and reported as sent.
const adminsTopic = "admins"

// topicPlane is the slice of notify provisioning the reconcile uses.
type topicPlane interface {
	EnsureSubscriber(ctx context.Context, account string) error
	EnsureTopic(ctx context.Context, key, name string) error
	AddTopicMember(ctx context.Context, key, account string) error
	RemoveTopicMember(ctx context.Context, key, account string) error
}

// accountBook is what the forge knows about who exists and what they
// hold — the truth the reconcile makes notify match.
type accountBook interface {
	Accounts() ([]oltp.Account, error)
	GrantsFor(account string) (map[string]oltp.Role, error)
}

// notifySync is what a reconcile did, for the startup log line.
type notifySync struct{ Subscribers, Members int }

// reconcileNotify makes notify's subscribers and topics match the
// forge's accounts and grants.
//
// Provisioning has always happened on the WRITE: account.add creates a
// subscriber, a grant joins the repository's activity topic. Every
// account and grant older than that code is therefore unprovisioned,
// and the failure is silent in the worst way — a trigger addressed to a
// topic that was never created is refused with a 404 the caller
// discards, so the forge reports nothing while nobody is told anything.
// This is the sweep that fixes the fleet rather than the next write.
//
// Every call it makes is idempotent, so it runs on every start. A
// failure on one account is collected and the sweep goes on: one
// account notify cannot hold must not cost the other nineteen their
// alerts.
func reconcileNotify(ctx context.Context, np topicPlane, book accountBook) (notifySync, error) {
	accounts, err := book.Accounts()
	if err != nil {
		return notifySync{}, err
	}
	var sync notifySync
	var errs []error
	if err := np.EnsureTopic(ctx, adminsTopic, "quarry admins"); err != nil {
		// Without the topic there is no point adding members to it, but
		// the repository topics below are independent and still worth
		// doing.
		errs = append(errs, err)
	}
	for _, a := range accounts {
		if err := np.EnsureSubscriber(ctx, a.Name); err != nil {
			errs = append(errs, err)
			// A subscriber that does not exist cannot join a topic;
			// notify answers those with a 422 that says exactly that.
			continue
		}
		sync.Subscribers++
		// Disabled is WHEN the account was shut, empty while open. A
		// shut account is not an admin for this purpose: alerts name
		// production faults, and someone who can no longer sign in
		// should not keep receiving them.
		if a.Admin && a.Disabled == "" {
			if err := np.AddTopicMember(ctx, adminsTopic, a.Name); err != nil {
				errs = append(errs, err)
			} else {
				sync.Members++
			}
		} else if err := np.RemoveTopicMember(ctx, adminsTopic, a.Name); err != nil {
			// Removing an absent member is already success in the
			// client, so this is a real failure.
			errs = append(errs, err)
		}
		if a.Disabled != "" {
			continue
		}
		grants, err := book.GrantsFor(a.Name)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		for repo := range grants {
			key := repoTopicKey(repo)
			if err := np.EnsureTopic(ctx, key, repo+" activity"); err != nil {
				errs = append(errs, err)
				continue
			}
			if err := np.AddTopicMember(ctx, key, a.Name); err != nil {
				errs = append(errs, err)
				continue
			}
			sync.Members++
		}
	}
	return sync, errors.Join(errs...)
}