github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/seed/seedtest/seedtest.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-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 seedtest
    21  
    22  import (
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/asserts/assertstest"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/seed"
    35  	"github.com/snapcore/snapd/seed/seedwriter"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/snap/naming"
    38  	"github.com/snapcore/snapd/snap/snapfile"
    39  	"github.com/snapcore/snapd/snap/snaptest"
    40  	"github.com/snapcore/snapd/timings"
    41  )
    42  
    43  // SeedSnaps helps creating snaps for a seed.
    44  type SeedSnaps struct {
    45  	StoreSigning *assertstest.StoreStack
    46  	Brands       *assertstest.SigningAccounts
    47  
    48  	snaps map[string]string
    49  	infos map[string]*snap.Info
    50  
    51  	snapAssertNow time.Time
    52  
    53  	snapRevs map[string]*asserts.SnapRevision
    54  }
    55  
    56  // SetupAssertSigning initializes StoreSigning for storeBrandID and Brands.
    57  func (ss *SeedSnaps) SetupAssertSigning(storeBrandID string) {
    58  	ss.StoreSigning = assertstest.NewStoreStack(storeBrandID, nil)
    59  	ss.Brands = assertstest.NewSigningAccounts(ss.StoreSigning)
    60  }
    61  
    62  func (ss *SeedSnaps) AssertedSnapID(snapName string) string {
    63  	snapID := naming.WellKnownSnapID(snapName)
    64  	if snapID != "" {
    65  		return snapID
    66  	}
    67  	return snaptest.AssertedSnapID(snapName)
    68  }
    69  
    70  func (ss *SeedSnaps) snapAssertionNow() time.Time {
    71  	if ss.snapAssertNow.IsZero() {
    72  		return time.Now()
    73  	}
    74  	return ss.snapAssertNow
    75  }
    76  
    77  func (ss *SeedSnaps) SetSnapAssertionNow(t time.Time) {
    78  	ss.snapAssertNow = t
    79  }
    80  
    81  func (ss *SeedSnaps) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string, dbs ...*asserts.Database) (*asserts.SnapDeclaration, *asserts.SnapRevision) {
    82  	info, err := snap.InfoFromSnapYaml([]byte(snapYaml))
    83  	c.Assert(err, IsNil)
    84  	snapName := info.SnapName()
    85  
    86  	snapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, files)
    87  
    88  	snapID := ss.AssertedSnapID(snapName)
    89  	declA, err := ss.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
    90  		"series":       "16",
    91  		"snap-id":      snapID,
    92  		"publisher-id": developerID,
    93  		"snap-name":    snapName,
    94  		"timestamp":    ss.snapAssertionNow().UTC().Format(time.RFC3339),
    95  	}, nil, "")
    96  	c.Assert(err, IsNil)
    97  
    98  	sha3_384, size, err := asserts.SnapFileSHA3_384(snapFile)
    99  	c.Assert(err, IsNil)
   100  
   101  	revA, err := ss.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
   102  		"snap-sha3-384": sha3_384,
   103  		"snap-size":     fmt.Sprintf("%d", size),
   104  		"snap-id":       snapID,
   105  		"developer-id":  developerID,
   106  		"snap-revision": revision.String(),
   107  		"timestamp":     ss.snapAssertionNow().UTC().Format(time.RFC3339),
   108  	}, nil, "")
   109  	c.Assert(err, IsNil)
   110  
   111  	if !revision.Unset() {
   112  		info.SnapID = snapID
   113  		info.Revision = revision
   114  	}
   115  
   116  	for _, db := range dbs {
   117  		err := db.Add(declA)
   118  		c.Assert(err, IsNil)
   119  		err = db.Add(revA)
   120  		c.Assert(err, IsNil)
   121  	}
   122  
   123  	if ss.snaps == nil {
   124  		ss.snaps = make(map[string]string)
   125  		ss.infos = make(map[string]*snap.Info)
   126  		ss.snapRevs = make(map[string]*asserts.SnapRevision)
   127  	}
   128  
   129  	ss.snaps[snapName] = snapFile
   130  	info.SideInfo.RealName = snapName
   131  	ss.infos[snapName] = info
   132  	snapDecl := declA.(*asserts.SnapDeclaration)
   133  	snapRev := revA.(*asserts.SnapRevision)
   134  	ss.snapRevs[snapName] = snapRev
   135  
   136  	return snapDecl, snapRev
   137  }
   138  
   139  func (ss *SeedSnaps) AssertedSnap(snapName string) (snapFile string) {
   140  	return ss.snaps[snapName]
   141  }
   142  
   143  func (ss *SeedSnaps) AssertedSnapInfo(snapName string) *snap.Info {
   144  	return ss.infos[snapName]
   145  }
   146  
   147  func (ss *SeedSnaps) AssertedSnapRevision(snapName string) *asserts.SnapRevision {
   148  	return ss.snapRevs[snapName]
   149  }
   150  
   151  // TestingSeed16 helps setting up a populated Core 16/18 testing seed.
   152  type TestingSeed16 struct {
   153  	SeedSnaps
   154  
   155  	SeedDir string
   156  }
   157  
   158  func (s *TestingSeed16) SnapsDir() string {
   159  	return filepath.Join(s.SeedDir, "snaps")
   160  }
   161  
   162  func (s *TestingSeed16) AssertsDir() string {
   163  	return filepath.Join(s.SeedDir, "assertions")
   164  }
   165  
   166  func (s *TestingSeed16) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string) (snapFname string, snapDecl *asserts.SnapDeclaration, snapRev *asserts.SnapRevision) {
   167  	decl, rev := s.SeedSnaps.MakeAssertedSnap(c, snapYaml, files, revision, developerID)
   168  
   169  	snapFile := s.snaps[decl.SnapName()]
   170  
   171  	snapFname = filepath.Base(snapFile)
   172  	targetFile := filepath.Join(s.SnapsDir(), snapFname)
   173  	err := os.Rename(snapFile, targetFile)
   174  	c.Assert(err, IsNil)
   175  
   176  	return snapFname, decl, rev
   177  }
   178  
   179  func (s *TestingSeed16) MakeModelAssertionChain(brandID, model string, extras ...map[string]interface{}) []asserts.Assertion {
   180  	assertChain := []asserts.Assertion{}
   181  	modelA := s.Brands.Model(brandID, model, extras...)
   182  
   183  	assertChain = append(assertChain, s.Brands.Account(modelA.BrandID()))
   184  	assertChain = append(assertChain, s.Brands.AccountKey(modelA.BrandID()))
   185  	assertChain = append(assertChain, modelA)
   186  
   187  	storeAccountKey := s.StoreSigning.StoreAccountKey("")
   188  	assertChain = append(assertChain, storeAccountKey)
   189  	return assertChain
   190  }
   191  
   192  func (s *TestingSeed16) WriteAssertions(fn string, assertions ...asserts.Assertion) {
   193  	fn = filepath.Join(s.AssertsDir(), fn)
   194  	WriteAssertions(fn, assertions...)
   195  }
   196  
   197  func WriteAssertions(fn string, assertions ...asserts.Assertion) {
   198  	f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0644)
   199  	if err != nil {
   200  		panic(err)
   201  	}
   202  	defer f.Close()
   203  	enc := asserts.NewEncoder(f)
   204  	for _, a := range assertions {
   205  		err := enc.Encode(a)
   206  		if err != nil {
   207  			panic(err)
   208  		}
   209  	}
   210  }
   211  
   212  func ReadAssertions(c *C, fn string) []asserts.Assertion {
   213  	f, err := os.Open(fn)
   214  	c.Assert(err, IsNil)
   215  
   216  	var as []asserts.Assertion
   217  	dec := asserts.NewDecoder(f)
   218  	for {
   219  		a, err := dec.Decode()
   220  		if err == io.EOF {
   221  			break
   222  		}
   223  		c.Assert(err, IsNil)
   224  		as = append(as, a)
   225  	}
   226  
   227  	return as
   228  }
   229  
   230  // TestingSeed20 helps setting up a populated Core 20 testing seed directory.
   231  type TestingSeed20 struct {
   232  	SeedSnaps
   233  
   234  	SeedDir string
   235  }
   236  
   237  // MakeSeed creates the seed with given label and generates model assertions
   238  func (s *TestingSeed20) MakeSeed(c *C, label, brandID, modelID string, modelHeaders map[string]interface{}, optSnaps []*seedwriter.OptionsSnap) *asserts.Model {
   239  	model := s.Brands.Model(brandID, modelID, modelHeaders)
   240  
   241  	assertstest.AddMany(s.StoreSigning, s.Brands.AccountsAndKeys(brandID)...)
   242  
   243  	s.MakeSeedWithModel(c, label, model, optSnaps)
   244  	return model
   245  }
   246  
   247  // MakeSeedWithModel creates the seed with given label for a given model
   248  func (s *TestingSeed20) MakeSeedWithModel(c *C, label string, model *asserts.Model, optSnaps []*seedwriter.OptionsSnap) {
   249  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
   250  		Backstore: asserts.NewMemoryBackstore(),
   251  		Trusted:   s.StoreSigning.Trusted,
   252  	})
   253  	c.Assert(err, IsNil)
   254  
   255  	retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
   256  		return ref.Resolve(s.StoreSigning.Find)
   257  	}
   258  	newFetcher := func(save func(asserts.Assertion) error) asserts.Fetcher {
   259  		save2 := func(a asserts.Assertion) error {
   260  			// for checking
   261  			err := db.Add(a)
   262  			if err != nil {
   263  				if _, ok := err.(*asserts.RevisionError); ok {
   264  					return nil
   265  				}
   266  				return err
   267  			}
   268  			return save(a)
   269  		}
   270  		return asserts.NewFetcher(db, retrieve, save2)
   271  	}
   272  
   273  	opts := seedwriter.Options{
   274  		SeedDir: s.SeedDir,
   275  		Label:   label,
   276  	}
   277  	w, err := seedwriter.New(model, &opts)
   278  	c.Assert(err, IsNil)
   279  
   280  	err = w.SetOptionsSnaps(optSnaps)
   281  	c.Assert(err, IsNil)
   282  
   283  	rf, err := w.Start(db, newFetcher)
   284  	c.Assert(err, IsNil)
   285  
   286  	localSnaps, err := w.LocalSnaps()
   287  	c.Assert(err, IsNil)
   288  
   289  	for _, sn := range localSnaps {
   290  		si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, rf, db)
   291  		if !asserts.IsNotFound(err) {
   292  			c.Assert(err, IsNil)
   293  		}
   294  		f, err := snapfile.Open(sn.Path)
   295  		c.Assert(err, IsNil)
   296  		info, err := snap.ReadInfoFromSnapFile(f, si)
   297  		c.Assert(err, IsNil)
   298  		w.SetInfo(sn, info)
   299  		sn.ARefs = aRefs
   300  	}
   301  
   302  	err = w.InfoDerived()
   303  	c.Assert(err, IsNil)
   304  
   305  	for {
   306  		snaps, err := w.SnapsToDownload()
   307  		c.Assert(err, IsNil)
   308  
   309  		for _, sn := range snaps {
   310  			name := sn.SnapName()
   311  
   312  			info := s.AssertedSnapInfo(name)
   313  			c.Assert(info, NotNil, Commentf("no snap info for %q", name))
   314  			err := w.SetInfo(sn, info)
   315  			c.Assert(err, IsNil)
   316  
   317  			prev := len(rf.Refs())
   318  			err = rf.Save(s.snapRevs[name])
   319  			c.Assert(err, IsNil)
   320  			sn.ARefs = rf.Refs()[prev:]
   321  
   322  			if _, err := os.Stat(sn.Path); err == nil {
   323  				// snap is already present
   324  				continue
   325  			}
   326  
   327  			err = os.Rename(s.AssertedSnap(name), sn.Path)
   328  			c.Assert(err, IsNil)
   329  		}
   330  
   331  		complete, err := w.Downloaded()
   332  		c.Assert(err, IsNil)
   333  		if complete {
   334  			break
   335  		}
   336  	}
   337  
   338  	copySnap := func(name, src, dst string) error {
   339  		return osutil.CopyFile(src, dst, 0)
   340  	}
   341  
   342  	err = w.SeedSnaps(copySnap)
   343  	c.Assert(err, IsNil)
   344  
   345  	err = w.WriteMeta()
   346  	c.Assert(err, IsNil)
   347  }
   348  
   349  func ValidateSeed(c *C, root, label string, usesSnapd bool, trusted []asserts.Assertion) seed.Seed {
   350  	tm := &timings.Timings{}
   351  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
   352  		Backstore: asserts.NewMemoryBackstore(),
   353  		Trusted:   trusted,
   354  	})
   355  	c.Assert(err, IsNil)
   356  	commitTo := func(b *asserts.Batch) error {
   357  		return b.CommitTo(db, nil)
   358  	}
   359  
   360  	sd, err := seed.Open(root, label)
   361  	c.Assert(err, IsNil)
   362  
   363  	err = sd.LoadAssertions(db, commitTo)
   364  	c.Assert(err, IsNil)
   365  
   366  	err = sd.LoadMeta(tm)
   367  	c.Assert(err, IsNil)
   368  
   369  	// core18/core20 use the snapd snap, old core does not
   370  	c.Check(sd.UsesSnapdSnap(), Equals, usesSnapd)
   371  	if usesSnapd {
   372  		// core*, kernel, gadget, snapd
   373  		c.Check(sd.EssentialSnaps(), HasLen, 4)
   374  	} else {
   375  		// core, kernel, gadget
   376  		c.Check(sd.EssentialSnaps(), HasLen, 3)
   377  	}
   378  	return sd
   379  }