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