github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/seed/seed16.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 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 seed
    21  
    22  /* ATTN this should *not* use:
    23  
    24  * dirs package: it is passed an explicit directory to work on
    25  
    26  * release.OnClassic: it assumes classic based on the model classic
    27    option; consistency between system and model can/must be enforced
    28    elsewhere
    29  
    30  */
    31  
    32  import (
    33  	"fmt"
    34  	"path/filepath"
    35  
    36  	"github.com/snapcore/snapd/asserts"
    37  	"github.com/snapcore/snapd/asserts/snapasserts"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/seed/internal"
    40  	"github.com/snapcore/snapd/snap"
    41  	"github.com/snapcore/snapd/snap/channel"
    42  	"github.com/snapcore/snapd/snap/naming"
    43  	"github.com/snapcore/snapd/timings"
    44  )
    45  
    46  type seed16 struct {
    47  	seedDir string
    48  
    49  	db asserts.RODatabase
    50  
    51  	model *asserts.Model
    52  
    53  	yamlSnaps []*internal.Snap16
    54  
    55  	essCache map[string]*Snap
    56  
    57  	snaps             []*Snap
    58  	essentialSnapsNum int
    59  
    60  	usesSnapdSnap bool
    61  }
    62  
    63  func (s *seed16) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
    64  	if db == nil {
    65  		// a db was not provided, create an internal temporary one
    66  		var err error
    67  		db, commitTo, err = newMemAssertionsDB(nil)
    68  		if err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	assertSeedDir := filepath.Join(s.seedDir, "assertions")
    74  	// collect assertions and find model assertion
    75  	var modelRef *asserts.Ref
    76  	checkForModel := func(ref *asserts.Ref) error {
    77  		if ref.Type == asserts.ModelType {
    78  			if modelRef != nil && modelRef.Unique() != ref.Unique() {
    79  				return fmt.Errorf("cannot have multiple model assertions in seed")
    80  			}
    81  			modelRef = ref
    82  		}
    83  		return nil
    84  	}
    85  
    86  	batch, err := loadAssertions(assertSeedDir, checkForModel)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// verify we have one model assertion
    92  	if modelRef == nil {
    93  		return fmt.Errorf("seed must have a model assertion")
    94  	}
    95  
    96  	if err := commitTo(batch); err != nil {
    97  		return err
    98  	}
    99  
   100  	a, err := modelRef.Resolve(db.Find)
   101  	if err != nil {
   102  		return fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err)
   103  	}
   104  
   105  	// remember db for later use
   106  	s.db = db
   107  	s.model = a.(*asserts.Model)
   108  
   109  	return nil
   110  }
   111  
   112  func (s *seed16) Model() *asserts.Model {
   113  	if s.model == nil {
   114  		panic("internal error: model assertion unset (LoadAssertions not called)")
   115  	}
   116  	return s.model
   117  }
   118  
   119  func (s *seed16) Brand() (*asserts.Account, error) {
   120  	return findBrand(s, s.db)
   121  }
   122  
   123  func (s *seed16) addSnap(sn *internal.Snap16, pinnedTrack string, cache map[string]*Snap, tm timings.Measurer) (*Snap, error) {
   124  	path := filepath.Join(s.seedDir, "snaps", sn.File)
   125  
   126  	seedSnap := cache[path]
   127  	if seedSnap == nil {
   128  		snapChannel := sn.Channel
   129  		if pinnedTrack != "" {
   130  			var err error
   131  			snapChannel, err = channel.ResolvePinned(pinnedTrack, snapChannel)
   132  			if err != nil {
   133  				// fallback to using the pinned track directly
   134  				snapChannel = pinnedTrack
   135  			}
   136  		}
   137  		seedSnap = &Snap{
   138  			Path:    path,
   139  			Channel: snapChannel,
   140  			Classic: sn.Classic,
   141  			DevMode: sn.DevMode,
   142  		}
   143  
   144  		var sideInfo snap.SideInfo
   145  		if sn.Unasserted {
   146  			sideInfo.RealName = sn.Name
   147  		} else {
   148  			var si *snap.SideInfo
   149  			var err error
   150  			timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", sn.Name), func(nested timings.Measurer) {
   151  				si, err = snapasserts.DeriveSideInfo(path, s.db)
   152  			})
   153  			if asserts.IsNotFound(err) {
   154  				return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path)
   155  			}
   156  			if err != nil {
   157  				return nil, err
   158  			}
   159  			sideInfo = *si
   160  			sideInfo.Private = sn.Private
   161  			sideInfo.Contact = sn.Contact
   162  		}
   163  
   164  		seedSnap.SideInfo = &sideInfo
   165  		if cache != nil {
   166  			cache[path] = seedSnap
   167  		}
   168  	}
   169  
   170  	s.snaps = append(s.snaps, seedSnap)
   171  
   172  	return seedSnap, nil
   173  }
   174  
   175  type essentialSnapMissingError struct {
   176  	SnapName string
   177  }
   178  
   179  func (e *essentialSnapMissingError) Error() string {
   180  	return fmt.Sprintf("essential snap %q required by the model is missing in the seed", e.SnapName)
   181  }
   182  
   183  func (s *seed16) loadYaml() error {
   184  	if s.yamlSnaps != nil {
   185  		return nil
   186  	}
   187  
   188  	seedYamlFile := filepath.Join(s.seedDir, "seed.yaml")
   189  	if !osutil.FileExists(seedYamlFile) {
   190  		return ErrNoMeta
   191  	}
   192  
   193  	seedYaml, err := internal.ReadSeedYaml(seedYamlFile)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	s.yamlSnaps = seedYaml.Snaps
   198  
   199  	return nil
   200  }
   201  
   202  func (s *seed16) resetSnaps() {
   203  	// setup essential snaps cache
   204  	if s.essCache == nil {
   205  		// 4 = snapd+base+kernel+gadget
   206  		s.essCache = make(map[string]*Snap, 4)
   207  	}
   208  
   209  	s.snaps = nil
   210  	s.essentialSnapsNum = 0
   211  }
   212  
   213  func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.SnapSet, added map[string]bool, tm timings.Measurer) error {
   214  	model := s.Model()
   215  
   216  	seeding := make(map[string]*internal.Snap16, len(s.yamlSnaps))
   217  	for _, sn := range s.yamlSnaps {
   218  		seeding[sn.Name] = sn
   219  	}
   220  
   221  	classic := model.Classic()
   222  	_, usesSnapdSnap := seeding["snapd"]
   223  	usesSnapdSnap = usesSnapdSnap || required.Contains(naming.Snap("snapd"))
   224  	s.usesSnapdSnap = usesSnapdSnap
   225  
   226  	baseSnap := "core"
   227  	classicWithSnapd := false
   228  	if model.Base() != "" {
   229  		baseSnap = model.Base()
   230  	}
   231  	if classic && s.usesSnapdSnap {
   232  		classicWithSnapd = true
   233  		// there is no system-wide base as such
   234  		// if there is a gadget we will install its base first though
   235  		baseSnap = ""
   236  	}
   237  
   238  	// add the essential snaps
   239  	addEssential := func(snapName string, pinnedTrack string, essType snap.Type) (*Snap, error) {
   240  		// be idempotent
   241  		if added[snapName] {
   242  			return nil, nil
   243  		}
   244  
   245  		// filter if required
   246  		if essentialTypes != nil {
   247  			skip := true
   248  			for _, t := range essentialTypes {
   249  				if t == essType {
   250  					skip = false
   251  					break
   252  				}
   253  			}
   254  			if skip {
   255  				return nil, nil
   256  			}
   257  		}
   258  
   259  		yamlSnap := seeding[snapName]
   260  		if yamlSnap == nil {
   261  			return nil, &essentialSnapMissingError{SnapName: snapName}
   262  		}
   263  
   264  		seedSnap, err := s.addSnap(yamlSnap, pinnedTrack, s.essCache, tm)
   265  		if err != nil {
   266  			return nil, err
   267  		}
   268  
   269  		if essType == snap.TypeBase && snapName == "core" {
   270  			essType = snap.TypeOS
   271  		}
   272  
   273  		seedSnap.EssentialType = essType
   274  		seedSnap.Essential = true
   275  		seedSnap.Required = true
   276  		added[snapName] = true
   277  
   278  		return seedSnap, nil
   279  	}
   280  
   281  	// if there are snaps to seed, core/base needs to be seeded too
   282  	if len(s.yamlSnaps) != 0 {
   283  		// ensure "snapd" snap is installed first
   284  		if model.Base() != "" || classicWithSnapd {
   285  			if _, err := addEssential("snapd", "", snap.TypeSnapd); err != nil {
   286  				return err
   287  			}
   288  		}
   289  		if !classicWithSnapd {
   290  			if _, err := addEssential(baseSnap, "", snap.TypeBase); err != nil {
   291  				return err
   292  			}
   293  		}
   294  	}
   295  
   296  	if kernelName := model.Kernel(); kernelName != "" {
   297  		if _, err := addEssential(kernelName, model.KernelTrack(), snap.TypeKernel); err != nil {
   298  			return err
   299  		}
   300  	}
   301  
   302  	if gadgetName := model.Gadget(); gadgetName != "" {
   303  		gadget, err := addEssential(gadgetName, model.GadgetTrack(), snap.TypeGadget)
   304  		if err != nil {
   305  			return err
   306  		}
   307  		// not skipped
   308  		if gadget != nil {
   309  
   310  			// always make sure the base of gadget is installed first
   311  			info, err := readInfo(gadget.Path, gadget.SideInfo)
   312  			if err != nil {
   313  				return err
   314  			}
   315  			gadgetBase := info.Base
   316  			if gadgetBase == "" {
   317  				gadgetBase = "core"
   318  			}
   319  			// Sanity check
   320  			// TODO: do we want to relax this? the new logic would allow
   321  			// but it might just be confusing for now
   322  			if baseSnap != "" && gadgetBase != baseSnap {
   323  				return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", gadgetBase, model.Base())
   324  			}
   325  			if _, err = addEssential(gadgetBase, "", snap.TypeBase); err != nil {
   326  				return err
   327  			}
   328  		}
   329  	}
   330  
   331  	s.essentialSnapsNum = len(s.snaps)
   332  
   333  	return nil
   334  }
   335  
   336  func (s *seed16) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
   337  	model := s.Model()
   338  
   339  	if err := s.loadYaml(); err != nil {
   340  		return err
   341  	}
   342  
   343  	required := naming.NewSnapSet(model.RequiredWithEssentialSnaps())
   344  	added := make(map[string]bool, 3)
   345  
   346  	s.resetSnaps()
   347  
   348  	return s.loadEssentialMeta(essentialTypes, required, added, tm)
   349  }
   350  
   351  func (s *seed16) LoadMeta(tm timings.Measurer) error {
   352  	model := s.Model()
   353  
   354  	if err := s.loadYaml(); err != nil {
   355  		return err
   356  	}
   357  
   358  	required := naming.NewSnapSet(model.RequiredWithEssentialSnaps())
   359  	added := make(map[string]bool, 3)
   360  
   361  	s.resetSnaps()
   362  
   363  	if err := s.loadEssentialMeta(nil, required, added, tm); err != nil {
   364  		return err
   365  	}
   366  
   367  	// the rest of the snaps
   368  	for _, sn := range s.yamlSnaps {
   369  		if added[sn.Name] {
   370  			continue
   371  		}
   372  		seedSnap, err := s.addSnap(sn, "", nil, tm)
   373  		if err != nil {
   374  			return err
   375  		}
   376  		if required.Contains(seedSnap) {
   377  			seedSnap.Required = true
   378  		}
   379  	}
   380  
   381  	return nil
   382  }
   383  
   384  func (s *seed16) UsesSnapdSnap() bool {
   385  	return s.usesSnapdSnap
   386  }
   387  
   388  func (s *seed16) EssentialSnaps() []*Snap {
   389  	return s.snaps[:s.essentialSnapsNum]
   390  }
   391  
   392  func (s *seed16) ModeSnaps(mode string) ([]*Snap, error) {
   393  	if mode != "run" {
   394  		return nil, fmt.Errorf("internal error: Core 16/18 have only run mode, got: %s", mode)
   395  	}
   396  	return s.snaps[s.essentialSnapsNum:], nil
   397  }
   398  
   399  func (s *seed16) NumSnaps() int {
   400  	return len(s.snaps)
   401  }
   402  
   403  func (s *seed16) Iter(f func(sn *Snap) error) error {
   404  	for _, sn := range s.snaps {
   405  		if err := f(sn); err != nil {
   406  			return err
   407  		}
   408  	}
   409  	return nil
   410  }