github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/batch.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 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) 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) 143 } 144 145 // commitTo does a best effort of adding all the batch assertions to 146 // the target database. 147 func (b *Batch) commitTo(db *Database) error { 148 if err := b.prereqSort(db); err != nil { 149 return err 150 } 151 152 // TODO: trigger w. caller a global sanity check if something is revoked 153 // (but try to save as much possible still), 154 // or err is a check error 155 156 var errs []error 157 for _, a := range b.added { 158 err := db.Add(a) 159 if IsUnaccceptedUpdate(err) { 160 // unsupported format case is handled before 161 // be idempotent 162 // system db has already the same or newer 163 continue 164 } 165 if err != nil { 166 errs = append(errs, err) 167 } 168 } 169 if len(errs) != 0 { 170 return &commitError{errs: errs} 171 } 172 return nil 173 } 174 175 func (b *Batch) prereqSort(db *Database) error { 176 if b.inPrereqOrder { 177 // nothing to do 178 return nil 179 } 180 181 // put in prereq order using a fetcher 182 ordered := make([]Assertion, 0, len(b.added)) 183 retrieve := func(ref *Ref) (Assertion, error) { 184 a, err := b.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat()) 185 if IsNotFound(err) { 186 // fallback to pre-existing assertions 187 a, err = ref.Resolve(db.Find) 188 } 189 if err != nil { 190 return nil, resolveError("cannot resolve prerequisite assertion: %s", ref, err) 191 } 192 return a, nil 193 } 194 save := func(a Assertion) error { 195 ordered = append(ordered, a) 196 return nil 197 } 198 f := NewFetcher(db, retrieve, save) 199 200 for _, a := range b.added { 201 if err := f.Fetch(a.Ref()); err != nil { 202 return err 203 } 204 } 205 206 b.added = ordered 207 b.inPrereqOrder = true 208 return nil 209 } 210 211 func resolveError(format string, ref *Ref, err error) error { 212 if IsNotFound(err) { 213 return fmt.Errorf(format, ref) 214 } else { 215 return fmt.Errorf(format+": %v", ref, err) 216 } 217 } 218 219 type commitError struct { 220 errs []error 221 } 222 223 func (e *commitError) Error() string { 224 l := []string{""} 225 for _, e := range e.errs { 226 l = append(l, e.Error()) 227 } 228 return fmt.Sprintf("cannot accept some assertions:%s", strings.Join(l, "\n - ")) 229 }