Documentation
¶
Overview ¶
Package projects holds the domain logic of the project registry: the registration every path that writes a projects row goes through, the point-in-time traversal that answers what a project is related to, and the cycle guard a new cost-attributing relation has to pass. The two walks are over project_relations rather than reads of one row, and the registration is one rule the HTTP handler and the admin CLI share, so all three live beside the queries instead of in the handlers. Phase 3 attribution walks the same graph and reuses this package.
The normative specification is roadmap/01-phase-1-core-platform-openstack.md, WP 1.9. The cloud a virtual project carries is decision D1 of roadmap/05-phase-5-commercial-pricing.md.
Index ¶
Constants ¶
const ( AuditObject = "projects" ActionCreate = AuditObject + ".create" )
The audit trail a registration leaves, whichever path wrote it.
Variables ¶
var ErrAlreadyRegistered = errors.New("a project with this cloud and external id is already registered")
ErrAlreadyRegistered refuses a (cloud, external_id) pair the registry holds.
var ErrCycle = errors.New("the relation would close a cycle over the attributing relation types")
ErrCycle is what GuardCycle returns for a relation that would close a cycle over the attributing relation types. Attribution is a forest, so a cycle in it would bill the same cost twice.
var ErrVirtualKey = errors.New("a project of a virtual platform carries its platform as its cloud, and no other project carries a virtual platform as its cloud")
ErrVirtualKey refuses a registration that breaks decision D1.
Functions ¶
func GuardCycle ¶
func GuardCycle(ctx context.Context, q *sqlcgen.Queries, source, target uuid.UUID, attributingTypes []string, ) error
GuardCycle reports whether a relation from source to target would close a cycle over attributingTypes. It walks the active relations of those types out of target and returns ErrCycle as soon as source is among them, an edge straight back to source included, and nil once the walk runs out of projects.
Only active relations count: one that was closed carries no cost any more. An empty attributingTypes finds no relation at all, so nothing is a cycle. The walk needs no depth cap, because a project is only ever visited once.
func Register ¶
func Register(ctx context.Context, q *sqlcgen.Queries, actor string, r Registration) (sqlcgen.Project, error)
Register writes the projects row and the audit row naming it through q, which is the handle of the caller's transaction: a registration the log does not account for never reaches the database. A pair that breaks decision D1 is refused before the database is touched, so a virtual project keeps carrying its platform as its cloud.
Platform, Cloud and ExternalID are not checked for emptiness. The contract's minLength: 1 on the three members of CreateProject covers the HTTP path, and the admin CLI refuses an empty external id and sets the other two from a constant.
Types ¶
type Registration ¶
type Registration struct {
Platform, Cloud, ExternalID string
Name pgtype.Text
Metadata json.RawMessage // the stored document; empty registers {}
}
Registration is what a caller registers.
type Related ¶
Related is one project a walk reached. RelationType is the type of the edge that reached it, Depth the number of edges between it and the project the walk started from, and Path the relation ids of those edges in walk order, so len(Path) == Depth.
func Traverse ¶
func Traverse(ctx context.Context, q *sqlcgen.Queries, start uuid.UUID, depth int, relationType *string, at time.Time, ) ([]Related, error)
Traverse walks the outgoing relations of start that are valid at at, up to depth edges out, and returns every project it reaches. A relationType that is not nil narrows the walk to relations of that type.
The walk is breadth-first and one query per level, so a depth of ten costs ten queries whatever the graph looks like. First visit wins: a project the walk has already reached is never reached again, which terminates a cycle and keeps start itself out of the result. The projects come back in the order they were visited, which is the order of the frontier over the order of the query, and nothing comes back for a depth below one or for a project with no outgoing relation valid at at.
Bounding depth is the caller's business: the API caps it at ten.