Files
bridge/internal/service/process_relationships.go

217 lines
6.4 KiB
Go

package service
import (
"context"
"go.infratographer.com/x/events"
"go.infratographer.com/x/gidx"
"go.equinixmetal.net/infra9-metal-bridge/internal/permissions"
)
// processRelationships determines the changes between what is wanted and what is live and executes on the differences.
// Relationship creations use events.
// Relationship deletions use the api, as delete events delete all related resources and not just the provided ones.
func (s *service) processRelationships(ctx context.Context, eventType string, relationships Relationships) int {
rlogger := s.logger.With("resource.id", relationships.Resource.PrefixedID())
wantParentRelationship, wantSubjectRelationships := s.mapRelationWants(relationships)
liveParentRelationships, liveSubjectRelationships, err := s.getRelationshipMap(ctx, relationships.Resource, relationships.SubjectRelation)
if err != nil {
rlogger.Errorw("failed to get relationship map",
"relationships.subject_relation", relationships.SubjectRelation,
"error", err,
)
return 0
}
var (
createParentRelationship *Relation
deleteParentRelationships []gidx.PrefixedID
foundParent bool
createSubjectRelationships []Relation
deleteSubjectRelationships []Relation
)
if wantParentRelationship != nil {
for subjID := range liveParentRelationships {
if subjID == wantParentRelationship.Resource.PrefixedID() {
foundParent = true
continue
}
deleteParentRelationships = append(deleteParentRelationships, subjID)
}
if !foundParent {
createParentRelationship = wantParentRelationship
}
} else {
for subjID := range liveParentRelationships {
deleteParentRelationships = append(deleteParentRelationships, subjID)
}
}
for resID, relation := range wantSubjectRelationships {
if _, ok := liveSubjectRelationships[resID]; ok {
continue
}
createSubjectRelationships = append(createSubjectRelationships, Relation{
Resource: prefixedID{resID},
Relation: relation,
})
}
for resID, relation := range liveSubjectRelationships {
if _, ok := wantSubjectRelationships[resID]; ok {
continue
}
deleteSubjectRelationships = append(deleteSubjectRelationships, Relation{
Resource: prefixedID{resID},
Relation: relation,
})
}
var processEvents []events.ChangeMessage
rlogger.Debugw("processing relationships",
"parent.create", createParentRelationship != nil,
"parent.delete", len(deleteParentRelationships),
"subject.create", len(createSubjectRelationships),
"subject.delete", len(deleteSubjectRelationships),
)
if createParentRelationship != nil {
processEvents = append(processEvents, events.ChangeMessage{
SubjectID: relationships.Resource.PrefixedID(),
EventType: string(events.CreateChangeType),
AdditionalSubjectIDs: []gidx.PrefixedID{
createParentRelationship.Resource.PrefixedID(),
},
})
}
for _, relation := range createSubjectRelationships {
processEvents = append(processEvents, events.ChangeMessage{
SubjectID: relation.Resource.PrefixedID(),
EventType: string(events.CreateChangeType),
AdditionalSubjectIDs: []gidx.PrefixedID{
relationships.Resource.PrefixedID(),
},
})
}
for _, relatedResourceID := range deleteParentRelationships {
err = s.perms.DeleteResourceRelationship(ctx, relationships.Resource.PrefixedID(), string(RelateParent), relatedResourceID)
if err != nil {
rlogger.Errorw("error deleting parent relationship",
"parent.resource.id", relatedResourceID.String(),
)
}
}
for _, relation := range deleteSubjectRelationships {
err = s.perms.DeleteResourceRelationship(ctx, relation.Resource.PrefixedID(), string(relation.Relation), relationships.Resource.PrefixedID())
if err != nil {
rlogger.Errorw("error deleting relationship",
"relation", relation.Relation,
"subject.id", relation.Resource.PrefixedID().String(),
)
}
}
for _, event := range processEvents {
err = s.publisher.PublishChange(ctx, eventType, event)
if err != nil {
rlogger.Errorw("error publishing change",
"subject_type", eventType,
"subject.id", event.SubjectID,
"event.type", event.EventType,
"additional_subject_ids", event.AdditionalSubjectIDs,
"error", err,
)
}
}
rlogger.Debugw("relationships processed",
"parent.create", createParentRelationship != nil,
"parent.delete", len(deleteParentRelationships),
"subject.create", len(createSubjectRelationships),
"subject.delete", len(deleteSubjectRelationships),
)
changes := len(deleteParentRelationships) + len(createSubjectRelationships) + len(deleteSubjectRelationships)
if createParentRelationship != nil {
changes++
}
return changes
}
// mapRelationWants returns the parent relation if provided and a map of Subjects -> relation.
func (s *service) mapRelationWants(relationships Relationships) (*Relation, map[gidx.PrefixedID]RelationshipType) {
var wantParent *Relation
wantSubject := make(map[gidx.PrefixedID]RelationshipType)
if relationships.Parent.Resource != nil {
wantParent = &relationships.Parent
}
for _, relationship := range relationships.SubjectRelationships {
wantSubject[relationship.Resource.PrefixedID()] = relationship.Relation
}
return wantParent, wantSubject
}
// getRelationshipMap fetches the provided resources relationships, as the source resource and the destination subject.
// Returned are two maps, the first maps Subject IDs -> Relationship
// The second map, maps Resource IDs -> relationship
func (s *service) getRelationshipMap(ctx context.Context, resource IDPrefixableResource, relation RelationshipType) (map[gidx.PrefixedID]RelationshipType, map[gidx.PrefixedID]RelationshipType, error) {
liveResource, err := s.perms.ListResourceRelationshipsFrom(ctx, resource.PrefixedID())
if err != nil {
return nil, nil, err
}
var liveSubject []permissions.ResourceRelationship
if relation != "" {
liveSubject, err = s.perms.ListResourceRelationshipsTo(ctx, resource.PrefixedID())
if err != nil {
return nil, nil, err
}
}
parents := make(map[gidx.PrefixedID]RelationshipType, len(liveResource))
for _, relationship := range liveResource {
if relationship.Relation != string(RelateParent) {
continue
}
parents[relationship.SubjectID] = RelationshipType(relationship.Relation)
}
subject := make(map[gidx.PrefixedID]RelationshipType, len(liveSubject))
for _, relationship := range liveSubject {
if relationship.Relation != string(relation) {
continue
}
subject[relationship.ResourceID] = RelationshipType(relationship.Relation)
}
return parents, subject, nil
}