mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
many: let provide a SnapHandler to Seed.Load*Meta*
Merge pull request #11710 from pedronis/seed-snap-handling This allows to perform dedicated handling for seed snaps (like copying them already) together with digest computation. SnapHandler is slightly complex because of unasserted vs asserted snaps and the differences how UC16/18 vs 20+ seeds are processed. Before there was always caching of essential snaps across LoadEssentialMeta* and LoadMeta, this is turned off if handlers are provided, OTOH specific caching can be done by handler logic if it makes sense.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// -*- Mode: Go; indent-tabs-mode: t -*-
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Canonical Ltd
|
||||
* Copyright (C) 2022 Canonical Ltd
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
@@ -86,13 +86,24 @@ func CrossCheck(instanceName, snapSHA3_384 string, snapSize uint64, si *snap.Sid
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeriveSideInfo tries to construct a SideInfo for the given snap using its digest to find the relevant snap assertions with the information in the given database. It will fail with an asserts.NotFoundError if it cannot find them.
|
||||
// DeriveSideInfo tries to construct a SideInfo for the given snap
|
||||
// using its digest to find the relevant snap assertions with the
|
||||
// information in the given database. It will fail with an
|
||||
// asserts.NotFoundError if it cannot find them.
|
||||
func DeriveSideInfo(snapPath string, db Finder) (*snap.SideInfo, error) {
|
||||
snapSHA3_384, snapSize, err := asserts.SnapFileSHA3_384(snapPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return DeriveSideInfoFromDigestAndSize(snapPath, snapSHA3_384, snapSize, db)
|
||||
}
|
||||
|
||||
// DeriveSideInfoFromDigestAndSize tries to construct a SideInfo
|
||||
// using digest and size as provided for the snap to find the relevant
|
||||
// snap assertions with the information in the given database. It will
|
||||
// fail with an asserts.NotFoundError if it cannot find them.
|
||||
func DeriveSideInfoFromDigestAndSize(snapPath string, snapSHA3_384 string, snapSize uint64, db Finder) (*snap.SideInfo, error) {
|
||||
// get relevant assertions and reconstruct metadata
|
||||
a, err := db.Find(asserts.SnapRevisionType, map[string]string{
|
||||
"snap-sha3-384": snapSHA3_384,
|
||||
|
||||
@@ -612,7 +612,7 @@ func (s *imageSuite) loadSeed(c *C, seeddir string) (essSnaps []*seed.Snap, runS
|
||||
err = sd.LoadAssertions(db, commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = sd.LoadMeta(seed.AllModes, timings.New(nil))
|
||||
err = sd.LoadMeta(seed.AllModes, nil, timings.New(nil))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
essSnaps = sd.EssentialSnaps()
|
||||
|
||||
@@ -118,7 +118,7 @@ func writePreseedAssertion(artifactDigest []byte, opts *preseedOpts) error {
|
||||
model := sd.Model()
|
||||
|
||||
tm := timings.New(nil)
|
||||
if err := sd.LoadMeta("run", tm); err != nil {
|
||||
if err := sd.LoadMeta("run", nil, tm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,11 @@ func (fs *FakeSeed) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Mea
|
||||
return fs.LoadMetaErr
|
||||
}
|
||||
|
||||
func (fs *FakeSeed) LoadMeta(mode string, tm timings.Measurer) error {
|
||||
func (fs *FakeSeed) LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler seed.SnapHandler, tm timings.Measurer) error {
|
||||
return fs.LoadMetaErr
|
||||
}
|
||||
|
||||
func (fs *FakeSeed) LoadMeta(mode string, handler seed.SnapHandler, tm timings.Measurer) error {
|
||||
return fs.LoadMetaErr
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// -*- Mode: Go; indent-tabs-mode: t -*-
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014-2020 Canonical Ltd
|
||||
* Copyright (C) 2014-2022 Canonical Ltd
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
@@ -126,7 +126,7 @@ func populateStateFromSeedImpl(st *state.State, opts *populateStateFromSeedOptio
|
||||
}
|
||||
|
||||
timings.Run(tm, "load-verified-snap-metadata", "load verified snap metadata from seed", func(nested timings.Measurer) {
|
||||
err = deviceSeed.LoadMeta(mode, nested)
|
||||
err = deviceSeed.LoadMeta(mode, nil, nested)
|
||||
})
|
||||
if release.OnClassic && err == seed.ErrNoMeta {
|
||||
if preseed {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// -*- Mode: Go; indent-tabs-mode: t -*-
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Canonical Ltd
|
||||
* Copyright (C) 2016-2022 Canonical Ltd
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/snapcore/snapd/asserts/sysdb"
|
||||
"github.com/snapcore/snapd/snap"
|
||||
"github.com/snapcore/snapd/snap/snapfile"
|
||||
"github.com/snapcore/snapd/timings"
|
||||
)
|
||||
|
||||
var trusted = sysdb.Trusted()
|
||||
@@ -150,3 +151,13 @@ func findBrand(seed Seed, db asserts.RODatabase) (*asserts.Account, error) {
|
||||
}
|
||||
return a.(*asserts.Account), nil
|
||||
}
|
||||
|
||||
type defaultSnapHandler struct{}
|
||||
|
||||
func (h defaultSnapHandler) HandleUnassertedSnap(name, path string, _ timings.Measurer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h defaultSnapHandler) HandleAndDigestAssertedSnap(name, path string, essType snap.Type, _ *asserts.SnapRevision, _ func(string, uint64) (snap.Revision, error), _ timings.Measurer) (string, uint64, error) {
|
||||
return asserts.SnapFileSHA3_384(path)
|
||||
}
|
||||
|
||||
37
seed/seed.go
37
seed/seed.go
@@ -106,6 +106,19 @@ type Seed interface {
|
||||
// It will panic if called before LoadAssertions.
|
||||
LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error
|
||||
|
||||
// LoadEssentialMetaWithSnapHandler loads the seed's snaps metadata
|
||||
// for the essential snaps with types in the essentialTypes
|
||||
// set while verifying them against assertions. It can return
|
||||
// ErrNoMeta if there is no metadata nor snaps in the seed,
|
||||
// this is legitimate only on classic. It can be called
|
||||
// multiple times if needed before invoking LoadMeta.
|
||||
// It will panic if called before LoadAssertions.
|
||||
// A SnapHandler can be passed to perform dedicated seed snap
|
||||
// handling at the same time as digest computation.
|
||||
// No caching of essential snaps across Load*Meta* methods is
|
||||
// performed if a handler is provided.
|
||||
LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler SnapHandler, tm timings.Measurer) error
|
||||
|
||||
// LoadMeta loads the seed and seed's snaps metadata while
|
||||
// verifying the underlying snaps against assertions. It can
|
||||
// return ErrNoMeta if there is no metadata nor snaps in the
|
||||
@@ -115,7 +128,11 @@ type Seed interface {
|
||||
// load the metadata only for the snaps of that mode.
|
||||
// At which point ModeSnaps will only accept that mode
|
||||
// and Iter and NumSnaps only consider the snaps for that mode.
|
||||
LoadMeta(mode string, tm timings.Measurer) error
|
||||
// An optional SnapHandler can be passed to perform dedicated
|
||||
// seed snap handling at the same time as digest computation.
|
||||
// No caching of essential snaps across Load*Meta* methods is
|
||||
// performed if a handler is provided.
|
||||
LoadMeta(mode string, handler SnapHandler, tm timings.Measurer) error
|
||||
|
||||
// UsesSnapdSnap returns whether the system as defined by the
|
||||
// seed will use the snapd snap, after LoadMeta.
|
||||
@@ -140,6 +157,24 @@ type Seed interface {
|
||||
Iter(f func(sn *Snap) error) error
|
||||
}
|
||||
|
||||
// A SnapHandler can be used to perform any dedicated handling of seed
|
||||
// snaps and their digest computation while seed snap metadata loading
|
||||
// and verification is being performed.
|
||||
type SnapHandler interface {
|
||||
// HandleAndDigestAssertedSnap should compute the digest of
|
||||
// the given snap and perform any dedicated
|
||||
// handling. essentialType is provided only for essential
|
||||
// snaps.
|
||||
// snapRev is provided by UC20+ seeds.
|
||||
// deriveRev is provided by UC16/18 seeds, it can be used
|
||||
// to get early access to the snap revision based on the digest.
|
||||
HandleAndDigestAssertedSnap(name, path string, essentialType snap.Type, snapRev *asserts.SnapRevision, deriveRev func(snapSHA3_384 string, snapSize uint64) (snap.Revision, error), tm timings.Measurer) (snapSHA3_384 string, snapSize uint64, err error)
|
||||
|
||||
// HandleUnassertedSnap should perfrom any dedicated handling
|
||||
// for the given unasserted snap.
|
||||
HandleUnassertedSnap(name, path string, tm timings.Measurer) error
|
||||
}
|
||||
|
||||
// Open returns a Seed implementation for the seed at seedDir.
|
||||
// label if not empty is used to identify a Core 20 recovery system seed.
|
||||
func Open(seedDir, label string) (Seed, error) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// -*- Mode: Go; indent-tabs-mode: t -*-
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014-2020 Canonical Ltd
|
||||
* Copyright (C) 2014-2022 Canonical Ltd
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
@@ -124,11 +124,16 @@ func (s *seed16) SetParallelism(int) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
func (s *seed16) addSnap(sn *internal.Snap16, pinnedTrack string, cache map[string]*Snap, tm timings.Measurer) (*Snap, error) {
|
||||
func (s *seed16) addSnap(sn *internal.Snap16, essType snap.Type, pinnedTrack string, handler SnapHandler, cache map[string]*Snap, tm timings.Measurer) (*Snap, error) {
|
||||
path := filepath.Join(s.seedDir, "snaps", sn.File)
|
||||
|
||||
_, defaultHandler := handler.(defaultSnapHandler)
|
||||
|
||||
seedSnap := cache[path]
|
||||
if seedSnap == nil {
|
||||
// not cached, or ignore the cache if a non-default handler
|
||||
// was passed, otherwise it would not be called which could be
|
||||
// unexpected
|
||||
if seedSnap == nil || !defaultHandler {
|
||||
snapChannel := sn.Channel
|
||||
if pinnedTrack != "" {
|
||||
var err error
|
||||
@@ -147,12 +152,33 @@ func (s *seed16) addSnap(sn *internal.Snap16, pinnedTrack string, cache map[stri
|
||||
|
||||
var sideInfo snap.SideInfo
|
||||
if sn.Unasserted {
|
||||
if err := handler.HandleUnassertedSnap(sn.Name, path, tm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sideInfo.RealName = sn.Name
|
||||
} else {
|
||||
var si *snap.SideInfo
|
||||
var err error
|
||||
|
||||
deriveRev := func(snapSHA3_384 string, snapSize uint64) (snap.Revision, error) {
|
||||
if si == nil {
|
||||
var err error
|
||||
si, err = snapasserts.DeriveSideInfoFromDigestAndSize(path, snapSHA3_384, snapSize, s.db)
|
||||
if err != nil {
|
||||
return snap.Revision{}, err
|
||||
}
|
||||
}
|
||||
return si.Revision, nil
|
||||
}
|
||||
timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", sn.Name), func(nested timings.Measurer) {
|
||||
si, err = snapasserts.DeriveSideInfo(path, s.db)
|
||||
var snapSHA3_384 string
|
||||
var snapSize uint64
|
||||
snapSHA3_384, snapSize, err = handler.HandleAndDigestAssertedSnap(sn.Name, path, essType, nil, deriveRev, tm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// sets si too
|
||||
_, err = deriveRev(snapSHA3_384, snapSize)
|
||||
})
|
||||
if asserts.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path)
|
||||
@@ -215,7 +241,7 @@ func (s *seed16) resetSnaps() {
|
||||
s.essentialSnapsNum = 0
|
||||
}
|
||||
|
||||
func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.SnapSet, added map[string]bool, tm timings.Measurer) error {
|
||||
func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.SnapSet, handler SnapHandler, added map[string]bool, tm timings.Measurer) error {
|
||||
model := s.Model()
|
||||
|
||||
seeding := make(map[string]*internal.Snap16, len(s.yamlSnaps))
|
||||
@@ -266,7 +292,7 @@ func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.
|
||||
return nil, &essentialSnapMissingError{SnapName: snapName}
|
||||
}
|
||||
|
||||
seedSnap, err := s.addSnap(yamlSnap, pinnedTrack, s.essCache, tm)
|
||||
seedSnap, err := s.addSnap(yamlSnap, essType, pinnedTrack, handler, s.essCache, tm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -339,6 +365,10 @@ func (s *seed16) loadEssentialMeta(essentialTypes []snap.Type, required *naming.
|
||||
}
|
||||
|
||||
func (s *seed16) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
|
||||
return s.LoadEssentialMetaWithSnapHandler(essentialTypes, nil, tm)
|
||||
}
|
||||
|
||||
func (s *seed16) LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler SnapHandler, tm timings.Measurer) error {
|
||||
model := s.Model()
|
||||
|
||||
if err := s.loadYaml(); err != nil {
|
||||
@@ -350,10 +380,17 @@ func (s *seed16) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measur
|
||||
|
||||
s.resetSnaps()
|
||||
|
||||
return s.loadEssentialMeta(essentialTypes, required, added, tm)
|
||||
if handler == nil {
|
||||
handler = defaultSnapHandler{}
|
||||
}
|
||||
|
||||
if len(essentialTypes) == 0 {
|
||||
essentialTypes = nil
|
||||
}
|
||||
return s.loadEssentialMeta(essentialTypes, required, handler, added, tm)
|
||||
}
|
||||
|
||||
func (s *seed16) LoadMeta(mode string, tm timings.Measurer) error {
|
||||
func (s *seed16) LoadMeta(mode string, handler SnapHandler, tm timings.Measurer) error {
|
||||
if mode != AllModes && mode != "run" {
|
||||
return fmt.Errorf("internal error: Core 16/18 have only run mode, got: %s", mode)
|
||||
}
|
||||
@@ -369,7 +406,11 @@ func (s *seed16) LoadMeta(mode string, tm timings.Measurer) error {
|
||||
|
||||
s.resetSnaps()
|
||||
|
||||
if err := s.loadEssentialMeta(nil, required, added, tm); err != nil {
|
||||
if handler == nil {
|
||||
handler = defaultSnapHandler{}
|
||||
}
|
||||
|
||||
if err := s.loadEssentialMeta(nil, required, handler, added, tm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -378,7 +419,7 @@ func (s *seed16) LoadMeta(mode string, tm timings.Measurer) error {
|
||||
if added[sn.Name] {
|
||||
continue
|
||||
}
|
||||
seedSnap, err := s.addSnap(sn, "", nil, tm)
|
||||
seedSnap, err := s.addSnap(sn, "", "", handler, nil, tm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// -*- Mode: Go; indent-tabs-mode: t -*-
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Canonical Ltd
|
||||
* Copyright (C) 2019-2022 Canonical Ltd
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
@@ -207,7 +207,7 @@ func (s *seed16Suite) TestLoadMetaNoMeta(c *C) {
|
||||
err = s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Check(err, Equals, seed.ErrNoMeta)
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func (s *seed16Suite) TestLoadMetaInvalidSeedYaml(c *C) {
|
||||
err = ioutil.WriteFile(filepath.Join(s.SeedDir, "seed.yaml"), content, 0644)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Check(err, ErrorMatches, `cannot read seed yaml: invalid risk in channel name: track/not-a-risk`)
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ func (s *seed16Suite) TestLoadMetaCore16Minimal(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
|
||||
@@ -470,7 +470,7 @@ func (s *seed16Suite) TestLoadMetaCore16(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
essSnaps := s.seed16.EssentialSnaps()
|
||||
@@ -508,7 +508,7 @@ func (s *seed16Suite) TestLoadMetaCore18Minimal(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
|
||||
@@ -564,7 +564,7 @@ func (s *seed16Suite) TestLoadMetaCore18(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
essSnaps := s.seed16.EssentialSnaps()
|
||||
@@ -635,7 +635,7 @@ func (s *seed16Suite) TestLoadMetaClassicNothing(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
|
||||
@@ -656,7 +656,7 @@ func (s *seed16Suite) TestLoadMetaClassicCore(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
|
||||
@@ -697,7 +697,7 @@ func (s *seed16Suite) TestLoadMetaClassicCoreWithGadget(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
|
||||
@@ -737,7 +737,7 @@ func (s *seed16Suite) TestLoadMetaClassicSnapd(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
|
||||
@@ -782,7 +782,7 @@ func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
|
||||
@@ -832,7 +832,7 @@ func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget18(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
|
||||
@@ -901,7 +901,7 @@ func (s *seed16Suite) TestLoadMetaCore18Local(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
essSnaps := s.seed16.EssentialSnaps()
|
||||
@@ -953,6 +953,86 @@ func (s *seed16Suite) TestLoadMetaCore18Local(c *C) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *seed16Suite) TestLoadMetaCore18SnapHandler(c *C) {
|
||||
localRequired18Seed := &seed.InternalSnap16{
|
||||
Name: "required18",
|
||||
Unasserted: true,
|
||||
DevMode: true,
|
||||
}
|
||||
s.makeSeed(c, map[string]interface{}{
|
||||
"base": "core18",
|
||||
"kernel": "pc-kernel=18",
|
||||
"gadget": "pc=18",
|
||||
"required-snaps": []interface{}{"core", "required18"},
|
||||
}, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, localRequired18Seed)
|
||||
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
h := newTestSnapHandler(s.SeedDir)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, h, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
essSnaps := s.seed16.EssentialSnaps()
|
||||
c.Check(essSnaps, HasLen, 4)
|
||||
|
||||
c.Check(essSnaps, DeepEquals, []*seed.Snap{
|
||||
{
|
||||
Path: s.expectedPath("snapd"),
|
||||
SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
|
||||
EssentialType: snap.TypeSnapd,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "stable",
|
||||
}, {
|
||||
Path: s.expectedPath("core18"),
|
||||
SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
|
||||
EssentialType: snap.TypeBase,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "stable",
|
||||
}, {
|
||||
Path: s.expectedPath("pc-kernel"),
|
||||
SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
|
||||
EssentialType: snap.TypeKernel,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "18",
|
||||
}, {
|
||||
Path: s.expectedPath("pc"),
|
||||
SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
|
||||
EssentialType: snap.TypeGadget,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "18",
|
||||
},
|
||||
})
|
||||
|
||||
runSnaps, err := s.seed16.ModeSnaps("run")
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(runSnaps, HasLen, 1)
|
||||
|
||||
c.Check(runSnaps, DeepEquals, []*seed.Snap{
|
||||
{
|
||||
Path: filepath.Join(s.SeedDir, "snaps", "required18_1.0_all.snap"),
|
||||
SideInfo: &snap.SideInfo{RealName: "required18"},
|
||||
Required: true,
|
||||
DevMode: true,
|
||||
},
|
||||
})
|
||||
|
||||
c.Check(h.asserted, DeepEquals, map[string]string{
|
||||
"snapd": "snaps/snapd_1.0_all.snap:snapd:1",
|
||||
"pc-kernel": "snaps/pc-kernel_1.0_all.snap:kernel:1",
|
||||
"core18": "snaps/core18_1.0_all.snap:base:1",
|
||||
"pc": "snaps/pc_1.0_all.snap:gadget:1",
|
||||
})
|
||||
c.Check(h.unasserted, DeepEquals, map[string]string{
|
||||
"required18": "snaps/required18_1.0_all.snap",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *seed16Suite) TestLoadMetaCore18StoreInfo(c *C) {
|
||||
s.makeSeed(c, map[string]interface{}{
|
||||
"base": "core18",
|
||||
@@ -963,7 +1043,7 @@ func (s *seed16Suite) TestLoadMetaCore18StoreInfo(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
essSnaps := s.seed16.EssentialSnaps()
|
||||
@@ -1013,7 +1093,7 @@ func (s *seed16Suite) TestLoadMetaCore18EnforcePinnedTracks(c *C) {
|
||||
err := s.seed16.LoadAssertions(s.db, s.commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = s.seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
|
||||
@@ -1134,7 +1214,7 @@ version: other-base
|
||||
testSeedSnap16s = t.breakSeed(testSeedSnap16s)
|
||||
s.writeSeed(c, testSeedSnap16s)
|
||||
|
||||
c.Check(seed16.LoadMeta(seed.AllModes, s.perfTimings), ErrorMatches, t.err)
|
||||
c.Check(seed16.LoadMeta(seed.AllModes, nil, s.perfTimings), ErrorMatches, t.err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1205,13 +1285,17 @@ func (s *seed16Suite) TestLoadEssentialMetaCore18(c *C) {
|
||||
{[]snap.Type{snap.TypeSnapd, snap.TypeKernel, snap.TypeBase}, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap}},
|
||||
{[]snap.Type{snap.TypeGadget, snap.TypeKernel}, []*seed.Snap{pcKernelSnap, pcSnap}},
|
||||
// degenerate case
|
||||
{[]snap.Type{}, []*seed.Snap(nil)},
|
||||
{[]snap.Type{}, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap, pcSnap}},
|
||||
{nil, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap, pcSnap}},
|
||||
}
|
||||
|
||||
for _, t := range tests {
|
||||
// hide the non-requested snaps to make sure they are not
|
||||
// accessed
|
||||
unhide := hideSnaps(c, all, t.onlyTypes)
|
||||
var unhide func()
|
||||
if len(t.onlyTypes) != 0 {
|
||||
unhide = hideSnaps(c, all, t.onlyTypes)
|
||||
}
|
||||
|
||||
seed16, err := seed.Open(s.SeedDir, "")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -1233,10 +1317,83 @@ func (s *seed16Suite) TestLoadEssentialMetaCore18(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(runSnaps, HasLen, 0)
|
||||
|
||||
unhide()
|
||||
if unhide != nil {
|
||||
unhide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *seed16Suite) TestLoadEssentialMetaWithSnapHandlerCore18(c *C) {
|
||||
r := seed.MockTrusted(s.StoreSigning.Trusted)
|
||||
defer r()
|
||||
|
||||
s.makeSeed(c, map[string]interface{}{
|
||||
"base": "core18",
|
||||
"kernel": "pc-kernel=18",
|
||||
"gadget": "pc=18",
|
||||
"required-snaps": []interface{}{"core", "required", "required18"},
|
||||
}, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, requiredSeed, coreSeed, required18Seed)
|
||||
|
||||
snapdSnap := &seed.Snap{
|
||||
Path: s.expectedPath("snapd"),
|
||||
SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
|
||||
EssentialType: snap.TypeSnapd,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "stable",
|
||||
}
|
||||
core18Snap := &seed.Snap{
|
||||
Path: s.expectedPath("core18"),
|
||||
SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
|
||||
EssentialType: snap.TypeBase,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "stable",
|
||||
}
|
||||
pcKernelSnap := &seed.Snap{
|
||||
Path: s.expectedPath("pc-kernel"),
|
||||
SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
|
||||
EssentialType: snap.TypeKernel,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "18",
|
||||
}
|
||||
pcSnap := &seed.Snap{
|
||||
Path: s.expectedPath("pc"),
|
||||
SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
|
||||
EssentialType: snap.TypeGadget,
|
||||
Essential: true,
|
||||
Required: true,
|
||||
Channel: "18",
|
||||
}
|
||||
|
||||
expected := []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap, pcSnap}
|
||||
|
||||
seed16, err := seed.Open(s.SeedDir, "")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = seed16.LoadAssertions(nil, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
h := newTestSnapHandler(s.SeedDir)
|
||||
|
||||
err = seed16.LoadEssentialMetaWithSnapHandler(nil, h, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(seed16.UsesSnapdSnap(), Equals, true)
|
||||
|
||||
essSnaps := seed16.EssentialSnaps()
|
||||
c.Check(essSnaps, HasLen, len(expected))
|
||||
c.Check(essSnaps, DeepEquals, expected)
|
||||
|
||||
c.Check(h.asserted, DeepEquals, map[string]string{
|
||||
"snapd": "snaps/snapd_1.0_all.snap:snapd:1",
|
||||
"pc-kernel": "snaps/pc-kernel_1.0_all.snap:kernel:1",
|
||||
"core18": "snaps/core18_1.0_all.snap:base:1",
|
||||
"pc": "snaps/pc_1.0_all.snap:gadget:1",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *seed16Suite) TestLoadEssentialAndMetaCore18(c *C) {
|
||||
r := seed.MockTrusted(s.StoreSigning.Trusted)
|
||||
defer r()
|
||||
@@ -1308,7 +1465,7 @@ func (s *seed16Suite) TestLoadEssentialAndMetaCore18(c *C) {
|
||||
// caching in place
|
||||
hideSnaps(c, []*seed.Snap{snapdSnap, core18Snap, pcKernelSnap}, nil)
|
||||
|
||||
err = seed16.LoadMeta(seed.AllModes, s.perfTimings)
|
||||
err = seed16.LoadMeta(seed.AllModes, nil, s.perfTimings)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(seed16.UsesSnapdSnap(), Equals, true)
|
||||
|
||||
@@ -268,7 +268,7 @@ func (e *noSnapDeclarationError) Error() string {
|
||||
return fmt.Sprintf("cannot find snap-declaration for snap name: %s", e.snapRef.SnapName())
|
||||
}
|
||||
|
||||
func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string) (snapPath string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, err error) {
|
||||
func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, essType snap.Type, handler SnapHandler, snapsDir string, tm timings.Measurer) (snapPath string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, err error) {
|
||||
snapID := snapRef.ID()
|
||||
if snapID != "" {
|
||||
snapDecl = s.snapDeclsByID[snapID]
|
||||
@@ -304,7 +304,7 @@ func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string)
|
||||
return "", nil, nil, fmt.Errorf("cannot validate %q for snap %q (snap-id %q), wrong size", snapPath, snapName, snapID)
|
||||
}
|
||||
|
||||
snapSHA3_384, _, err := asserts.SnapFileSHA3_384(snapPath)
|
||||
snapSHA3_384, _, err := handler.HandleAndDigestAssertedSnap(snapName, snapPath, essType, snapRev, nil, tm)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
@@ -317,7 +317,7 @@ func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string)
|
||||
return snapPath, snapRev, snapDecl, nil
|
||||
}
|
||||
|
||||
func (s *seed20) lookupSnap(snapRef naming.SnapRef, optSnap *internal.Snap20, channel string, snapsDir string, tm timings.Measurer) (*Snap, error) {
|
||||
func (s *seed20) lookupSnap(snapRef naming.SnapRef, essType snap.Type, optSnap *internal.Snap20, channel string, handler SnapHandler, snapsDir string, tm timings.Measurer) (*Snap, error) {
|
||||
if optSnap != nil && optSnap.Channel != "" {
|
||||
channel = optSnap.Channel
|
||||
}
|
||||
@@ -330,15 +330,18 @@ func (s *seed20) lookupSnap(snapRef naming.SnapRef, optSnap *internal.Snap20, ch
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read unasserted snap: %v", err)
|
||||
}
|
||||
sideInfo = &snap.SideInfo{RealName: info.SnapName()}
|
||||
if err := handler.HandleUnassertedSnap(info.SnapName(), path, tm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// suppress channel
|
||||
sideInfo = &snap.SideInfo{RealName: info.SnapName()}
|
||||
channel = ""
|
||||
} else {
|
||||
var err error
|
||||
timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", snapRef.SnapName()), func(nested timings.Measurer) {
|
||||
var snapRev *asserts.SnapRevision
|
||||
var snapDecl *asserts.SnapDeclaration
|
||||
path, snapRev, snapDecl, err = s.lookupVerifiedRevision(snapRef, snapsDir)
|
||||
path, snapRev, snapDecl, err = s.lookupVerifiedRevision(snapRef, essType, handler, snapsDir, tm)
|
||||
if err == nil {
|
||||
sideInfo = snapasserts.SideInfoFromSnapAssertions(snapDecl, snapRev)
|
||||
}
|
||||
@@ -377,15 +380,19 @@ type snapToConsider struct {
|
||||
|
||||
var errSkipped = errors.New("skipped optional snap")
|
||||
|
||||
func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, tm timings.Measurer) (*Snap, error) {
|
||||
func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, handler SnapHandler, tm timings.Measurer) (*Snap, error) {
|
||||
var snapRef naming.SnapRef
|
||||
var channel string
|
||||
var snapsDir string
|
||||
var essential bool
|
||||
var essType snap.Type
|
||||
var required bool
|
||||
if sntoc.modelSnap != nil {
|
||||
snapRef = sntoc.modelSnap
|
||||
essential = sntoc.essential
|
||||
if essential {
|
||||
essType = snapTypeFromModel(sntoc.modelSnap)
|
||||
}
|
||||
required = essential || sntoc.modelSnap.Presence == "required"
|
||||
channel = sntoc.modelSnap.DefaultChannel
|
||||
snapsDir = "../../snaps"
|
||||
@@ -394,7 +401,7 @@ func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, tm timings.Measurer) (*Sna
|
||||
channel = "latest/stable"
|
||||
snapsDir = "snaps"
|
||||
}
|
||||
seedSnap, err := s.lookupSnap(snapRef, sntoc.optSnap, channel, snapsDir, tm)
|
||||
seedSnap, err := s.lookupSnap(snapRef, essType, sntoc.optSnap, channel, handler, snapsDir, tm)
|
||||
if err != nil {
|
||||
if _, ok := err.(*noSnapDeclarationError); ok && !required {
|
||||
// skipped optional snap is ok
|
||||
@@ -418,26 +425,37 @@ func (s *seed20) doLoadMetaOne(sntoc *snapToConsider, tm timings.Measurer) (*Sna
|
||||
// we need to add the gadget base here
|
||||
}
|
||||
|
||||
seedSnap.EssentialType = snapTypeFromModel(sntoc.modelSnap)
|
||||
seedSnap.EssentialType = essType
|
||||
}
|
||||
return seedSnap, nil
|
||||
}
|
||||
|
||||
func (s *seed20) doLoadMeta(tm timings.Measurer) error {
|
||||
// setup essential snaps cache
|
||||
if s.essCache == nil {
|
||||
// 4 = snapd+base+kernel+gadget
|
||||
s.essCache = make(map[string]*Snap, 4)
|
||||
}
|
||||
cacheEssential := func(snType string, essSnap *Snap) {
|
||||
s.essCacheMu.Lock()
|
||||
defer s.essCacheMu.Unlock()
|
||||
s.essCache[snType] = essSnap
|
||||
}
|
||||
cachedEssential := func(snType string) *Snap {
|
||||
s.essCacheMu.Lock()
|
||||
defer s.essCacheMu.Unlock()
|
||||
return s.essCache[snType]
|
||||
func (s *seed20) doLoadMeta(handler SnapHandler, tm timings.Measurer) error {
|
||||
var cacheEssential func(snType string, essSnap *Snap)
|
||||
var cachedEssential func(snType string) *Snap
|
||||
if handler != nil {
|
||||
// ignore caching if not using the default handler
|
||||
// otherwise it would not always be called which could
|
||||
// be unexpected
|
||||
cacheEssential = func(string, *Snap) {}
|
||||
cachedEssential = func(string) *Snap { return nil }
|
||||
} else {
|
||||
handler = defaultSnapHandler{}
|
||||
// setup essential snaps cache
|
||||
if s.essCache == nil {
|
||||
// 4 = snapd+base+kernel+gadget
|
||||
s.essCache = make(map[string]*Snap, 4)
|
||||
}
|
||||
cacheEssential = func(snType string, essSnap *Snap) {
|
||||
s.essCacheMu.Lock()
|
||||
defer s.essCacheMu.Unlock()
|
||||
s.essCache[snType] = essSnap
|
||||
}
|
||||
cachedEssential = func(snType string) *Snap {
|
||||
s.essCacheMu.Lock()
|
||||
defer s.essCacheMu.Unlock()
|
||||
return s.essCache[snType]
|
||||
}
|
||||
}
|
||||
runMode := []string{"run"}
|
||||
|
||||
@@ -478,7 +496,7 @@ func (s *seed20) doLoadMeta(tm timings.Measurer) error {
|
||||
}
|
||||
if seedSnap == nil {
|
||||
var err error
|
||||
seedSnap, err = s.doLoadMetaOne(&sntoc, jtm)
|
||||
seedSnap, err = s.doLoadMetaOne(&sntoc, handler, jtm)
|
||||
if err != nil {
|
||||
if err == errSkipped {
|
||||
continue
|
||||
@@ -549,7 +567,7 @@ func (s *seed20) considerModelSnap(modelSnap *asserts.ModelSnap, essential bool,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *seed20) LoadMeta(mode string, tm timings.Measurer) error {
|
||||
func (s *seed20) LoadMeta(mode string, handler SnapHandler, tm timings.Measurer) error {
|
||||
const otherSnapsFollow = true
|
||||
if err := s.queueEssentialMeta(nil, otherSnapsFollow, tm); err != nil {
|
||||
return err
|
||||
@@ -574,10 +592,14 @@ func (s *seed20) LoadMeta(mode string, tm timings.Measurer) error {
|
||||
}
|
||||
}
|
||||
|
||||
return s.doLoadMeta(tm)
|
||||
return s.doLoadMeta(handler, tm)
|
||||
}
|
||||
|
||||
func (s *seed20) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
|
||||
return s.LoadEssentialMetaWithSnapHandler(essentialTypes, nil, tm)
|
||||
}
|
||||
|
||||
func (s *seed20) LoadEssentialMetaWithSnapHandler(essentialTypes []snap.Type, handler SnapHandler, tm timings.Measurer) error {
|
||||
var filterEssential func(*asserts.ModelSnap) bool
|
||||
if len(essentialTypes) != 0 {
|
||||
filterEssential = essentialSnapTypesToModelFilter(essentialTypes)
|
||||
@@ -589,7 +611,7 @@ func (s *seed20) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measur
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.doLoadMeta(tm)
|
||||
err := s.doLoadMeta(handler, tm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -363,7 +363,7 @@ func ValidateSeed(c *C, root, label string, usesSnapd bool, trusted []asserts.As
|
||||
err = sd.LoadAssertions(db, commitTo)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = sd.LoadMeta(seed.AllModes, tm)
|
||||
err = sd.LoadMeta(seed.AllModes, nil, tm)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// core18/core20 use the snapd snap, old core does not
|
||||
|
||||
@@ -96,7 +96,7 @@ func ValidateFromYaml(seedYamlFile string) error {
|
||||
}
|
||||
|
||||
tm := timings.New(nil)
|
||||
if err := seed.LoadMeta(AllModes, tm); err != nil {
|
||||
if err := seed.LoadMeta(AllModes, nil, tm); err != nil {
|
||||
if missingErr, ok := err.(*essentialSnapMissingError); ok {
|
||||
if seed.Model().Classic() && missingErr.SnapName == "core" {
|
||||
err = fmt.Errorf("essential snap core or snapd must be part of the seed")
|
||||
|
||||
@@ -44,7 +44,7 @@ environment:
|
||||
UBUNTU_IMAGE_SNAP_CHANNEL: "latest/candidate"
|
||||
# controls whether ubuntu-image is built using the current snapd tree as a
|
||||
# dependency or the one listed in its go.mod
|
||||
UBUNTU_IMAGE_ALLOW_API_BREAK: '$(HOST: echo "${SPREAD_UBUNTU_IMAGE_ALLOW_API_BREAK:-true}")'
|
||||
UBUNTU_IMAGE_ALLOW_API_BREAK: '$(HOST: echo "${SPREAD_UBUNTU_IMAGE_ALLOW_API_BREAK:-false}")'
|
||||
CORE_CHANNEL: '$(HOST: echo "${SPREAD_CORE_CHANNEL:-edge}")'
|
||||
BASE_CHANNEL: '$(HOST: echo "${SPREAD_BASE_CHANNEL:-edge}")'
|
||||
KERNEL_CHANNEL: '$(HOST: echo "${SPREAD_KERNEL_CHANNEL:-edge}")'
|
||||
|
||||
Reference in New Issue
Block a user