github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/asserts/batch.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "fmt" 24 "io" 25 "strings" 26 ) 27 28 // Batch allows to accumulate a set of assertions possibly out of 29 // prerequisite order and then add them in one go to an assertion 30 // database. 31 // Nothing will be committed if there are missing prerequisites, for a full 32 // consistency check beforehand there is the Precheck option. 33 type Batch struct { 34 bs Backstore 35 added []Assertion 36 // added is in prereq order 37 inPrereqOrder bool 38 39 unsupported func(u *Ref, err error) error 40 } 41 42 // NewBatch creates a new Batch to accumulate assertions to add in one 43 // go to an assertion database. 44 // unsupported can be used to ignore/log assertions with unsupported formats, 45 // default behavior is to error on them. 46 func NewBatch(unsupported func(u *Ref, err error) error) *Batch { 47 if unsupported == nil { 48 unsupported = func(_ *Ref, err error) error { 49 return err 50 } 51 } 52 53 return &Batch{ 54 bs: NewMemoryBackstore(), 55 inPrereqOrder: true, // empty list is trivially so 56 unsupported: unsupported, 57 } 58 } 59 60 // Add one assertion to the batch. 61 func (b *Batch) Add(a Assertion) error { 62 b.inPrereqOrder = false 63 64 if !a.SupportedFormat() { 65 err := &UnsupportedFormatError{Ref: a.Ref(), Format: a.Format()} 66 return b.unsupported(a.Ref(), err) 67 } 68 if err := b.bs.Put(a.Type(), a); err != nil { 69 if revErr, ok := err.(*RevisionError); ok { 70 if revErr.Current >= a.Revision() { 71 // we already got something more recent 72 return nil 73 } 74 } 75 return err 76 } 77 b.added = append(b.added, a) 78 return nil 79 } 80 81 // AddStream adds a stream of assertions to the batch. 82 // Returns references to the assertions effectively added. 83 func (b *Batch) AddStream(r io.Reader) ([]*Ref, error) { 84 b.inPrereqOrder = false 85 86 start := len(b.added) 87 dec := NewDecoder(r) 88 for { 89 a, err := dec.Decode() 90 if err == io.EOF { 91 break 92 } 93 if err != nil { 94 return nil, err 95 } 96 if err := b.Add(a); err != nil { 97 return nil, err 98 } 99 } 100 added := b.added[start:] 101 if len(added) == 0 { 102 return nil, nil 103 } 104 refs := make([]*Ref, len(added)) 105 for i, a := range added { 106 refs[i] = a.Ref() 107 } 108 return refs, nil 109 } 110 111 // Fetch adds to the batch by invoking fetching to drive an internal 112 // Fetcher that was built with trustedDB and retrieve. 113 func (b *Batch) Fetch(trustedDB RODatabase, retrieve func(*Ref) (Assertion, error), fetching func(Fetcher) error) error { 114 f := NewFetcher(trustedDB, retrieve, b.Add) 115 return fetching(f) 116 } 117 118 func (b *Batch) precheck(db *Database) error { 119 db = db.WithStackedBackstore(NewMemoryBackstore()) 120 return b.commitTo(db, nil) 121 } 122 123 type CommitOptions struct { 124 // Precheck indicates whether to do a full consistency check 125 // before starting adding the batch. 126 Precheck bool 127 } 128 129 // CommitTo adds the batch of assertions to the given assertion database. 130 // Nothing will be committed if there are missing prerequisites, for a full 131 // consistency check beforehand there is the Precheck option. 132 func (b *Batch) CommitTo(db *Database, opts *CommitOptions) error { 133 if opts == nil { 134 opts = &CommitOptions{} 135 } 136 if opts.Precheck { 137 if err := b.precheck(db); err != nil { 138 return err 139 } 140 } 141 142 return b.commitTo(db, nil) 143 } 144 145 // CommitToAndObserve adds the batch of assertions to the given 146 // assertion database while invoking observe for each one after they 147 // are added. 148 // Nothing will be committed if there are missing prerequisites, for a 149 // full consistency check beforehand there is the Precheck option. 150 // For convenience observe can be nil in which case is ignored. 151 func (b *Batch) CommitToAndObserve(db *Database, observe func(Assertion), opts *CommitOptions) error { 152 if opts == nil { 153 opts = &CommitOptions{} 154 } 155 if opts.Precheck { 156 if err := b.precheck(db); err != nil { 157 return err 158 } 159 } 160 161 return b.commitTo(db, observe) 162 } 163 164 // commitTo does a best effort of adding all the batch assertions to 165 // the target database. 166 func (b *Batch) commitTo(db *Database, observe func(Assertion)) error { 167 if err := b.prereqSort(db); err != nil { 168 return err 169 } 170 171 // TODO: trigger w. caller a global sanity check if something is revoked 172 // (but try to save as much possible still), 173 // or err is a check error 174 175 var errs []error 176 for _, a := range b.added { 177 err := db.Add(a) 178 if IsUnaccceptedUpdate(err) { 179 // unsupported format case is handled before 180 // be idempotent 181 // system db has already the same or newer 182 continue 183 } 184 if err != nil { 185 errs = append(errs, err) 186 } else if observe != nil { 187 observe(a) 188 } 189 } 190 if len(errs) != 0 { 191 return &commitError{errs: errs} 192 } 193 return nil 194 } 195 196 func (b *Batch) prereqSort(db *Database) error { 197 if b.inPrereqOrder { 198 // nothing to do 199 return nil 200 } 201 202 // put in prereq order using a fetcher 203 ordered := make([]Assertion, 0, len(b.added)) 204 retrieve := func(ref *Ref) (Assertion, error) { 205 a, err := b.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat()) 206 if IsNotFound(err) { 207 // fallback to pre-existing assertions 208 a, err = ref.Resolve(db.Find) 209 } 210 if err != nil { 211 return nil, resolveError("cannot resolve prerequisite assertion: %s", ref, err) 212 } 213 return a, nil 214 } 215 save := func(a Assertion) error { 216 ordered = append(ordered, a) 217 return nil 218 } 219 f := NewFetcher(db, retrieve, save) 220 221 for _, a := range b.added { 222 if err := f.Fetch(a.Ref()); err != nil { 223 return err 224 } 225 } 226 227 b.added = ordered 228 b.inPrereqOrder = true 229 return nil 230 } 231 232 func resolveError(format string, ref *Ref, err error) error { 233 if IsNotFound(err) { 234 return fmt.Errorf(format, ref) 235 } else { 236 return fmt.Errorf(format+": %v", ref, err) 237 } 238 } 239 240 type commitError struct { 241 errs []error 242 } 243 244 func (e *commitError) Error() string { 245 l := []string{""} 246 for _, e := range e.errs { 247 l = append(l, e.Error()) 248 } 249 return fmt.Sprintf("cannot accept some assertions:%s", strings.Join(l, "\n - ")) 250 }