gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/seed/seedwriter/seed20.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 seedwriter
    21  
    22  import (
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/seed/internal"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/snap/channel"
    33  	"github.com/snapcore/snapd/snap/naming"
    34  )
    35  
    36  type policy20 struct {
    37  	model *asserts.Model
    38  	opts  *Options
    39  
    40  	warningf func(format string, a ...interface{})
    41  }
    42  
    43  var errNotAllowedExceptForDangerous = errors.New("cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous")
    44  
    45  func (pol *policy20) checkAllowedDangerous() error {
    46  	if pol.model.Grade() != asserts.ModelDangerous {
    47  		return errNotAllowedExceptForDangerous
    48  	}
    49  	return nil
    50  }
    51  
    52  func (pol *policy20) allowsDangerousFeatures() error {
    53  	return pol.checkAllowedDangerous()
    54  }
    55  
    56  func (pol *policy20) checkDefaultChannel(channel.Channel) error {
    57  	return pol.checkAllowedDangerous()
    58  }
    59  
    60  func (pol *policy20) checkSnapChannel(ch channel.Channel, whichSnap string) error {
    61  	return pol.checkAllowedDangerous()
    62  }
    63  
    64  func (pol *policy20) systemSnap() *asserts.ModelSnap {
    65  	return internal.MakeSystemSnap("snapd", "latest/stable", []string{"run", "ephemeral"})
    66  }
    67  
    68  func (pol *policy20) modelSnapDefaultChannel() string {
    69  	// We will use latest/stable as default, model that want something else
    70  	// will need to to speficy a default-channel
    71  	return "latest/stable"
    72  }
    73  
    74  func (pol *policy20) extraSnapDefaultChannel() string {
    75  	// We will use latest/stable as default for consistency with
    76  	// model snaps, this means not taking into account default-tracks
    77  	// by default
    78  	return "latest/stable"
    79  }
    80  
    81  func (pol *policy20) checkBase(info *snap.Info, modes []string, availableByMode map[string]*naming.SnapSet) error {
    82  	base := info.Base
    83  	if base == "" {
    84  		if info.Type() != snap.TypeGadget && info.Type() != snap.TypeApp {
    85  			return nil
    86  		}
    87  		base = "core"
    88  	}
    89  
    90  	if pol.checkAvailable(naming.Snap(base), modes, availableByMode) {
    91  		return nil
    92  	}
    93  
    94  	whichBase := fmt.Sprintf("its base %q", base)
    95  	if base == "core16" {
    96  		if pol.checkAvailable(naming.Snap("core"), modes, availableByMode) {
    97  			return nil
    98  		}
    99  		whichBase += ` (or "core")`
   100  	}
   101  
   102  	return fmt.Errorf("cannot add snap %q without also adding %s explicitly%s", info.SnapName(), whichBase, errorMsgForModesSuffix(modes))
   103  }
   104  
   105  func (pol *policy20) checkAvailable(snapRef naming.SnapRef, modes []string, availableByMode map[string]*naming.SnapSet) bool {
   106  	// checks that snapRef is available in all modes
   107  	for _, mode := range modes {
   108  		byMode := availableByMode[mode]
   109  		if !byMode.Contains(snapRef) {
   110  			if mode == "run" || mode == "ephemeral" {
   111  				// no additional fallback for these
   112  				// cases:
   113  				// * run is not ephemeral,
   114  				//   is covered only by run
   115  				// * ephemeral is only covered by ephemeral
   116  				return false
   117  			}
   118  			// all non-run modes (e.g. recover) are
   119  			// considered ephemeral, as a fallback check
   120  			// if the snap is listed under the ephemeral mode label
   121  			ephem := availableByMode["ephemeral"]
   122  			if ephem == nil || !ephem.Contains(snapRef) {
   123  				return false
   124  			}
   125  		}
   126  	}
   127  	return true
   128  }
   129  
   130  func (pol *policy20) needsImplicitSnaps(map[string]*naming.SnapSet) (bool, error) {
   131  	// no implicit snaps with Core 20
   132  	// TODO: unless we want to support them for extra snaps
   133  	return false, nil
   134  }
   135  
   136  func (pol *policy20) implicitSnaps(map[string]*naming.SnapSet) []*asserts.ModelSnap {
   137  	return nil
   138  }
   139  
   140  func (pol *policy20) implicitExtraSnaps(map[string]*naming.SnapSet) []*OptionsSnap {
   141  	return nil
   142  }
   143  
   144  type tree20 struct {
   145  	grade asserts.ModelGrade
   146  	opts  *Options
   147  
   148  	snapsDirPath string
   149  	systemDir    string
   150  
   151  	systemSnapsDirEnsured bool
   152  }
   153  
   154  func (tr *tree20) mkFixedDirs() error {
   155  	tr.snapsDirPath = filepath.Join(tr.opts.SeedDir, "snaps")
   156  	tr.systemDir = filepath.Join(tr.opts.SeedDir, "systems", tr.opts.Label)
   157  
   158  	if err := os.MkdirAll(tr.snapsDirPath, 0755); err != nil {
   159  		return err
   160  	}
   161  
   162  	if err := os.MkdirAll(filepath.Dir(tr.systemDir), 0755); err != nil {
   163  		return err
   164  	}
   165  	if err := os.Mkdir(tr.systemDir, 0755); err != nil {
   166  		if os.IsExist(err) {
   167  			return &SystemAlreadyExistsError{
   168  				label: tr.opts.Label,
   169  			}
   170  		}
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  
   176  func (tr *tree20) ensureSystemSnapsDir() (string, error) {
   177  	snapsDir := filepath.Join(tr.systemDir, "snaps")
   178  	if tr.systemSnapsDirEnsured {
   179  		return snapsDir, nil
   180  	}
   181  	if err := os.MkdirAll(snapsDir, 0755); err != nil {
   182  		return "", err
   183  	}
   184  	tr.systemSnapsDirEnsured = true
   185  	return snapsDir, nil
   186  }
   187  
   188  func (tr *tree20) snapPath(sn *SeedSnap) (string, error) {
   189  	var snapsDir string
   190  	if sn.modelSnap != nil {
   191  		snapsDir = tr.snapsDirPath
   192  	} else {
   193  		// extra snap
   194  		var err error
   195  		snapsDir, err = tr.ensureSystemSnapsDir()
   196  		if err != nil {
   197  			return "", err
   198  		}
   199  	}
   200  	return filepath.Join(snapsDir, sn.Info.Filename()), nil
   201  }
   202  
   203  func (tr *tree20) localSnapPath(sn *SeedSnap) (string, error) {
   204  	sysSnapsDir, err := tr.ensureSystemSnapsDir()
   205  	if err != nil {
   206  		return "", err
   207  	}
   208  	return filepath.Join(sysSnapsDir, fmt.Sprintf("%s_%s.snap", sn.SnapName(), sn.Info.Version)), nil
   209  }
   210  
   211  func (tr *tree20) writeAssertions(db asserts.RODatabase, modelRefs []*asserts.Ref, snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error {
   212  	assertsDir := filepath.Join(tr.systemDir, "assertions")
   213  	if err := os.MkdirAll(assertsDir, 0755); err != nil {
   214  		return err
   215  	}
   216  
   217  	writeByRefs := func(fname string, refsGen func(stop <-chan struct{}) <-chan *asserts.Ref) error {
   218  		f, err := os.OpenFile(filepath.Join(assertsDir, fname), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
   219  		if err != nil {
   220  			return err
   221  		}
   222  		defer f.Close()
   223  
   224  		stop := make(chan struct{})
   225  		defer close(stop)
   226  		refs := refsGen(stop)
   227  
   228  		enc := asserts.NewEncoder(f)
   229  		for {
   230  			aRef := <-refs
   231  			if aRef == nil {
   232  				break
   233  			}
   234  			a, err := aRef.Resolve(db.Find)
   235  			if err != nil {
   236  				return fmt.Errorf("internal error: lost saved assertion")
   237  			}
   238  			if err := enc.Encode(a); err != nil {
   239  				return err
   240  			}
   241  		}
   242  		return nil
   243  	}
   244  
   245  	pushRef := func(refs chan<- *asserts.Ref, ref *asserts.Ref, stop <-chan struct{}) bool {
   246  		select {
   247  		case refs <- ref:
   248  			return true
   249  		case <-stop:
   250  			// get unstuck if we error early
   251  			return false
   252  		}
   253  	}
   254  
   255  	modelOnly := func(aRef *asserts.Ref) bool { return aRef.Type == asserts.ModelType }
   256  	excludeModel := func(aRef *asserts.Ref) bool { return aRef.Type != asserts.ModelType }
   257  
   258  	modelRefsGen := func(include func(*asserts.Ref) bool) func(stop <-chan struct{}) <-chan *asserts.Ref {
   259  		return func(stop <-chan struct{}) <-chan *asserts.Ref {
   260  			refs := make(chan *asserts.Ref)
   261  			go func() {
   262  				for _, aRef := range modelRefs {
   263  					if include(aRef) {
   264  						if !pushRef(refs, aRef, stop) {
   265  							return
   266  						}
   267  					}
   268  				}
   269  				close(refs)
   270  			}()
   271  			return refs
   272  		}
   273  	}
   274  
   275  	if err := writeByRefs("../model", modelRefsGen(modelOnly)); err != nil {
   276  		return err
   277  	}
   278  
   279  	if err := writeByRefs("model-etc", modelRefsGen(excludeModel)); err != nil {
   280  		return err
   281  	}
   282  
   283  	snapsRefGen := func(snaps []*SeedSnap) func(stop <-chan struct{}) <-chan *asserts.Ref {
   284  		return func(stop <-chan struct{}) <-chan *asserts.Ref {
   285  			refs := make(chan *asserts.Ref)
   286  			go func() {
   287  				for _, sn := range snaps {
   288  					for _, aRef := range sn.ARefs {
   289  						if !pushRef(refs, aRef, stop) {
   290  							return
   291  						}
   292  					}
   293  				}
   294  				close(refs)
   295  			}()
   296  			return refs
   297  		}
   298  	}
   299  
   300  	if err := writeByRefs("snaps", snapsRefGen(snapsFromModel)); err != nil {
   301  		return err
   302  	}
   303  
   304  	if len(extraSnaps) != 0 {
   305  		if err := writeByRefs("extra-snaps", snapsRefGen(extraSnaps)); err != nil {
   306  			return err
   307  		}
   308  	}
   309  
   310  	return nil
   311  }
   312  
   313  func (tr *tree20) writeMeta(snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error {
   314  	var optionsSnaps []*internal.Snap20
   315  
   316  	for _, sn := range snapsFromModel {
   317  		channelOverride := ""
   318  		if sn.Channel != sn.modelSnap.DefaultChannel {
   319  			channelOverride = sn.Channel
   320  		}
   321  		if sn.Info.ID() != "" && channelOverride == "" {
   322  			continue
   323  		}
   324  		unasserted := ""
   325  		if sn.Info.ID() == "" {
   326  			unasserted = filepath.Base(sn.Path)
   327  		}
   328  
   329  		optionsSnaps = append(optionsSnaps, &internal.Snap20{
   330  			Name: sn.SnapName(),
   331  			// even if unasserted != "" SnapID is useful
   332  			// to cross-ref the model entry
   333  			SnapID:     sn.modelSnap.ID(),
   334  			Unasserted: unasserted,
   335  			Channel:    channelOverride,
   336  		})
   337  	}
   338  
   339  	for _, sn := range extraSnaps {
   340  		channel := sn.Channel
   341  		unasserted := ""
   342  		if sn.Info.ID() == "" {
   343  			unasserted = filepath.Base(sn.Path)
   344  			channel = ""
   345  		}
   346  
   347  		optionsSnaps = append(optionsSnaps, &internal.Snap20{
   348  			Name:       sn.SnapName(),
   349  			SnapID:     sn.Info.ID(),
   350  			Unasserted: unasserted,
   351  			Channel:    channel,
   352  		})
   353  	}
   354  
   355  	if len(optionsSnaps) != 0 {
   356  		if tr.grade != asserts.ModelDangerous {
   357  			return fmt.Errorf("internal error: unexpected non-model snap overrides with grade %s", tr.grade)
   358  		}
   359  		options20 := &internal.Options20{Snaps: optionsSnaps}
   360  		if err := options20.Write(filepath.Join(tr.systemDir, "options.yaml")); err != nil {
   361  			return err
   362  		}
   363  	}
   364  
   365  	auxInfos := make(map[string]*internal.AuxInfo20)
   366  
   367  	addAuxInfos := func(seedSnaps []*SeedSnap) {
   368  		for _, sn := range seedSnaps {
   369  			if sn.Info.ID() != "" {
   370  				if sn.Info.EditedContact != "" || sn.Info.Private {
   371  					auxInfos[sn.Info.ID()] = &internal.AuxInfo20{
   372  						Private: sn.Info.Private,
   373  						// TODO: set this only if the snap has no links
   374  						Contact: sn.Info.Contact(),
   375  					}
   376  				}
   377  			}
   378  		}
   379  	}
   380  
   381  	addAuxInfos(snapsFromModel)
   382  	addAuxInfos(extraSnaps)
   383  
   384  	if len(auxInfos) == 0 {
   385  		// nothing to do
   386  		return nil
   387  	}
   388  
   389  	if _, err := tr.ensureSystemSnapsDir(); err != nil {
   390  		return err
   391  	}
   392  
   393  	f, err := os.OpenFile(filepath.Join(tr.systemDir, "snaps", "aux-info.json"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
   394  	if err != nil {
   395  		return err
   396  	}
   397  	defer f.Close()
   398  	enc := json.NewEncoder(f)
   399  
   400  	if err := enc.Encode(auxInfos); err != nil {
   401  		return err
   402  	}
   403  
   404  	return nil
   405  }