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  }