Skip to content

Commit

Permalink
a/snapasserts: keep track of component constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewphelpsj committed Sep 18, 2024
1 parent 56a1f30 commit 4c38de1
Show file tree
Hide file tree
Showing 2 changed files with 347 additions and 27 deletions.
167 changes: 140 additions & 27 deletions asserts/snapasserts/validation_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,36 @@ func NewInstalledSnap(name, snapID string, revision snap.Revision, components []
// ValidationSetsConflictError describes an error where multiple
// validation sets are in conflict about snaps.
type ValidationSetsConflictError struct {
Sets map[string]*asserts.ValidationSet
Snaps map[string]error
Sets map[string]*asserts.ValidationSet
Snaps map[string]error
Components map[string]map[string]error
}

func (e *ValidationSetsConflictError) Error() string {
buf := bytes.NewBufferString("validation sets are in conflict:")
for _, err := range e.Snaps {
seen := make(map[string]bool)
for id, err := range e.Snaps {
fmt.Fprintf(buf, "\n- %v", err)
seen[id] = true

// if we have any component errors, we should put them next to the snap
// errors
for _, err := range e.Components[id] {
fmt.Fprintf(buf, "\n- %v", err)
}
}

for id, errs := range e.Components {
// if we've already seen the snap, then we have already printed the
// component errors
if seen[id] {
continue
}
for _, err := range errs {
fmt.Fprintf(buf, "\n- %v", err)
}
}

return buf.String()
}

Expand Down Expand Up @@ -207,6 +228,8 @@ func (e *ValidationSetsValidationError) Error() string {
}
}

// TODO: add components to error message

return buf.String()
}

Expand All @@ -226,7 +249,12 @@ var invalidPresRevision = snap.R(-1)

type snapConstraints struct {
constraints
componentConstraints map[string]constraints
componentConstraints map[string]*componentConstraints
}

// TODO: this type isn't needed, maybe makes things a bit clearer though?
type componentConstraints struct {
constraints
}

type constraints struct {
Expand Down Expand Up @@ -254,7 +282,7 @@ type revConstraint struct {
presence asserts.Presence
}

func (c *snapConstraints) conflict() *snapConflictsError {
func constraintsConflicts(c constraints) *conflictsError {
if c.presence != presConflict {
return nil
}
Expand Down Expand Up @@ -290,14 +318,33 @@ func (c *snapConstraints) conflict() *snapConflictsError {
}
}

return &snapConflictsError{
name: c.name,
revisions: byRev,
containerType := "snap"
if c.compRef != nil {
containerType = "component"
}

return &conflictsError{
name: c.name,
containerType: containerType,
revisions: byRev,
}
}

type snapConflictsError struct {
name string
func (c *snapConstraints) conflicts() (snap *conflictsError, components map[string]*conflictsError) {
componentConflicts := make(map[string]*conflictsError)
for _, cstrs := range c.componentConstraints {
conflict := constraintsConflicts(cstrs.constraints)
if conflict != nil {
componentConflicts[cstrs.name] = conflict
}
}

return constraintsConflicts(c.constraints), componentConflicts
}

type conflictsError struct {
name string
containerType string
// revisions maps revisions to validation-set keys of the sets
// that are in conflict over the revision.
// * unspecifiedRevision is used for validation-sets conflicting
Expand All @@ -308,12 +355,12 @@ type snapConflictsError struct {
revisions map[snap.Revision][]string
}

func (e *snapConflictsError) Error() string {
func (e *conflictsError) Error() string {
whichSets := func(which []string) string {
return fmt.Sprintf("(%s)", strings.Join(which, ","))
}

msg := fmt.Sprintf("cannot constrain snap %q", e.name)
msg := fmt.Sprintf("cannot constrain %s %q", e.containerType, e.name)
invalid := false
if invalidOnes, ok := e.revisions[invalidPresRevision]; ok {
msg += fmt.Sprintf(" as both invalid %s and required", whichSets(invalidOnes))
Expand Down Expand Up @@ -421,6 +468,54 @@ func (v *ValidationSets) Add(valset *asserts.ValidationSet) error {
return nil
}

// TODO: maybe a method on snapContraints?
func addComponents(sc *snapConstraints, comps map[string]asserts.ValidationSetComponent, validationSetKey string) {
for name, comp := range comps {
addComponent(sc, name, comp, validationSetKey)
}
}

// TODO: maybe a method on snapContraints?
func addComponent(sc *snapConstraints, compName string, comp asserts.ValidationSetComponent, validationSetKey string) {
rev := snap.R(comp.Revision)
if comp.Presence == asserts.PresenceInvalid {
rev = invalidPresRevision
}

rc := revConstraint{
validationSetKey: validationSetKey,
presence: comp.Presence,
}

compRef := naming.NewComponentRef(sc.name, compName)

cs := sc.componentConstraints[compName]
if cs == nil {
sc.componentConstraints[compName] = &componentConstraints{
constraints: constraints{
name: compName,
presence: comp.Presence,
revisions: map[snap.Revision][]revConstraint{
rev: {rc},
},
compRef: &compRef,
snapRef: sc.snapRef,
},
}
return
}

cs.revisions[rev] = append(cs.revisions[rev], rc)

// this counts really different revisions or invalid
ndiff := len(cs.revisions)
if _, ok := cs.revisions[unspecifiedRevision]; ok {
ndiff--
}

cs.presence = derivePresence(cs.presence, comp.Presence, ndiff)
}

func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey string) {
rev := snap.R(sn.Revision)
if sn.Presence == asserts.PresenceInvalid {
Expand All @@ -432,8 +527,8 @@ func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey
presence: sn.Presence,
}

cs := v.snaps[sn.SnapID]
if cs == nil {
sc := v.snaps[sn.SnapID]
if sc == nil {
v.snaps[sn.SnapID] = &snapConstraints{
constraints: constraints{
name: sn.Name,
Expand All @@ -443,20 +538,23 @@ func (v *ValidationSets) addSnap(sn *asserts.ValidationSetSnap, validationSetKey
},
snapRef: sn,
},
componentConstraints: make(map[string]constraints),
componentConstraints: make(map[string]*componentConstraints),
}
addComponents(v.snaps[sn.SnapID], sn.Components, validationSetKey)
return
}

cs.revisions[rev] = append(cs.revisions[rev], rc)
addComponents(sc, sn.Components, validationSetKey)

sc.revisions[rev] = append(sc.revisions[rev], rc)

// this counts really different revisions or invalid
ndiff := len(cs.revisions)
if _, ok := cs.revisions[unspecifiedRevision]; ok {
ndiff := len(sc.revisions)
if _, ok := sc.revisions[unspecifiedRevision]; ok {
ndiff--
}

cs.presence = derivePresence(cs.presence, sn.Presence, ndiff)
sc.presence = derivePresence(sc.presence, sn.Presence, ndiff)
}

func derivePresence(currentPresence, incomingPresence asserts.Presence, revisions int) asserts.Presence {
Expand Down Expand Up @@ -490,23 +588,38 @@ func derivePresence(currentPresence, incomingPresence asserts.Presence, revision
func (v *ValidationSets) Conflict() error {
sets := make(map[string]*asserts.ValidationSet)
snaps := make(map[string]error)
components := make(map[string]map[string]error)

for snapID, snConstrs := range v.snaps {
snConflictsErr := snConstrs.conflict()
if snConflictsErr != nil {
snaps[snapID] = snConflictsErr
for _, valsetKeys := range snConflictsErr.revisions {
snapConflicts, componentConflicts := snConstrs.conflicts()
if snapConflicts != nil {
snaps[snapID] = snapConflicts
for _, valsetKeys := range snapConflicts.revisions {
for _, valsetKey := range valsetKeys {
sets[valsetKey] = v.sets[valsetKey]
}
}
}

if len(componentConflicts) != 0 {
components[snapID] = make(map[string]error)
}

for _, conflicts := range componentConflicts {
components[snapID][conflicts.name] = conflicts
for _, valsetKeys := range conflicts.revisions {
for _, valsetKey := range valsetKeys {
sets[valsetKey] = v.sets[valsetKey]
}
}
}
}

if len(snaps) != 0 {
if len(snaps) != 0 || len(components) != 0 {
return &ValidationSetsConflictError{
Sets: sets,
Snaps: snaps,
Sets: sets,
Snaps: snaps,
Components: components,
}
}
return nil
Expand Down Expand Up @@ -686,7 +799,7 @@ func (v *ValidationSets) checkInstalledComponents(snaps installedSnapSet, ignore

componentConstraints := make([]constraints, 0, len(sc.componentConstraints))
for _, cstrs := range sc.componentConstraints {
componentConstraints = append(componentConstraints, cstrs)
componentConstraints = append(componentConstraints, cstrs.constraints)
}

compInstalledFn := componentInstalled(sc.name)
Expand Down
Loading

0 comments on commit 4c38de1

Please sign in to comment.