github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  }