Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

image: write resolved-content from snap prepare-image #9914

Merged
merged 18 commits into from Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
685fdcf
image: write resolved-content from snap prepare-image
mvo5 Feb 5, 2021
a49b00b
fix writeResolvedContent to go over all volumes
mvo5 Feb 25, 2021
a03a7fe
image: use symlink in resolved-content for the system-seed
mvo5 Feb 25, 2021
099d50e
image: pass prepareImageDir dir to writeResolvedContentImpl to make i…
mvo5 Feb 25, 2021
575408a
image/helpers.go: use absolute directory for prepareDir
anonymouse64 Feb 25, 2021
7cab875
image: tweak signature of WriteResolvedContent and include targetDir …
mvo5 Feb 26, 2021
61c72ed
image: no need to check for `!opts.Classic` in setupSeed()
mvo5 Feb 26, 2021
8b94daa
image: add test for WriteResolvedContent from a relative dir
mvo5 Feb 26, 2021
b0855d0
image: improve signature/docstring of writeResolvedContentImpl (thank…
mvo5 Mar 1, 2021
6b0b4a1
image: rename unpack{Gadget,Kernel} -> unpackSnap (and remove duplica…
mvo5 Mar 1, 2021
acb726a
image: update signature of writeResolvedContentImpl()
mvo5 Mar 1, 2021
de179c1
Merge remote-tracking branch 'upstream/master' into kernel-dtb-refs-6
mvo5 Mar 1, 2021
e754cf6
image: drop SectorSize in writeResolvedContentImpl()
mvo5 Mar 1, 2021
59dcc9b
image/helpers_test.go: sort find output to ensure the test is reprodu…
anonymouse64 Mar 3, 2021
6e976b1
gadget: add new gadget.DefaultConstraints (thanks to Maciej)
mvo5 Mar 5, 2021
0b7d742
image: write resolved volume-content to <volname>/part<structure-nr>
mvo5 Mar 5, 2021
8750384
Merge remote-tracking branch 'upstream/master' into kernel-dtb-refs-6
mvo5 Mar 8, 2021
1ca8eda
image: tweak comments about resolved-content layout (thanks to Samuele)
mvo5 Mar 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions image/export_test.go
Expand Up @@ -20,6 +20,7 @@
package image

import (
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/store"
)
Expand Down Expand Up @@ -50,4 +51,14 @@ func (opts *DownloadOptions) Validate() error {
var (
ErrRevisionAndCohort = errRevisionAndCohort
ErrPathInBase = errPathInBase

WriteResolvedContent = writeResolvedContent
)

func MockWriteResolvedContent(f func(prepareImageDir string, info *gadget.Info, gadgetRoot, kernelRoot string) error) (restore func()) {
oldWriteResolvedContent := writeResolvedContent
writeResolvedContent = f
return func() {
writeResolvedContent = oldWriteResolvedContent
}
}
59 changes: 59 additions & 0 deletions image/helpers.go
Expand Up @@ -40,6 +40,8 @@ import (

"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/auth"
Expand Down Expand Up @@ -404,3 +406,60 @@ func (tsto *ToolingStore) Find(at *asserts.AssertionType, headers map[string]str
}
return tsto.sto.Assertion(at, pk, tsto.user)
}

// var so that it can be mocked for tests
var writeResolvedContent = writeResolvedContentImpl

// writeResolvedContent takes gadget.Info and the unpacked
// gadget/kernel snaps and outputs the resolved content from the
// {gadget,kernel}.yaml into a filesystem tree with the structure:
// <prepareImageDir>/resolved-content/<volume-name>/<structure-name>/...
//
// E.g.
// /tmp/prep-img/resolved-content/pi/ubuntu-seed/{config.txt,bootcode.bin,...}
func writeResolvedContentImpl(prepareDir string, info *gadget.Info, gadgetUnpackDir, kernelUnpackDir string) error {
fullPrepareDir, err := filepath.Abs(prepareDir)
if err != nil {
return err
}
targetDir := filepath.Join(fullPrepareDir, "resolved-content")

constraints := gadget.LayoutConstraints{
NonMBRStartOffset: 1 * quantity.OffsetMiB,
mvo5 marked this conversation as resolved.
Show resolved Hide resolved
// TODO:UC20: SectorSize is irrelevant here, we only care
// about filesystem assets
SectorSize: 512,
}
for volName, vol := range info.Volumes {
pvol, err := gadget.LayoutVolume(gadgetUnpackDir, kernelUnpackDir, vol, constraints)
if err != nil {
return err
}
for _, ps := range pvol.LaidOutStructure {
if !ps.HasFilesystem() {
continue
}
mw, err := gadget.NewMountedFilesystemWriter(&ps, nil)
if err != nil {
return err
}
dst := filepath.Join(targetDir, volName, ps.Name)
// on UC20, ensure system-seed links back to the
// <PrepareDir>/system-seed
if ps.Role == gadget.SystemSeed {
uc20systemSeedDir := filepath.Join(fullPrepareDir, "system-seed")
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
if err := os.Symlink(uc20systemSeedDir, dst); err != nil {
mvo5 marked this conversation as resolved.
Show resolved Hide resolved
return err
}
}
if err := mw.Write(dst, nil); err != nil {
return err
}
}
}

return nil
}
119 changes: 119 additions & 0 deletions image/helpers_test.go
Expand Up @@ -21,14 +21,17 @@ package image_test

import (
"os"
"os/exec"
"path/filepath"
"runtime"

"gopkg.in/check.v1"

"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/image"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
)

func (s *imageSuite) TestDownloadpOptionsString(c *check.C) {
Expand Down Expand Up @@ -130,3 +133,119 @@ func (s *imageSuite) TestDownloadSnap(c *check.C) {

c.Check(logbuf.String(), check.Matches, `.* DEBUG: Going to download snap "core" `+opts.String()+".\n")
}

var validGadgetYaml = `
volumes:
vol1:
bootloader: grub
structure:
- name: non-fs
type: bare
size: 512
offset: 0
content:
- image: non-fs.img
- name: ubuntu-seed
role: system-seed
type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
size: 100M
filesystem: ext4
content:
- source: system-seed.efi
target: EFI/boot/system-seed.efi
- name: structure-name
role: system-boot
type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
size: 100M
filesystem: ext4
content:
- source: grubx64.efi
target: EFI/boot/grubx64.efi
- name: ubuntu-data
role: system-data
type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
size: 100M
vol2:
structure:
- name: struct2
type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
size: 100M
filesystem: ext4
content:
- source: foo
target: subdir/foo
`

func (s *imageSuite) TestWriteResolvedContent(c *check.C) {
prepareImageDir := c.MkDir()

s.testWriteResolvedContent(c, prepareImageDir)
}

func (s *imageSuite) TestWriteResolvedContentRelativePath(c *check.C) {
prepareImageDir := c.MkDir()

// chdir to prepareImage dir and run writeResolvedContent from
// this relative dir
cwd, err := os.Getwd()
c.Assert(err, check.IsNil)
err = os.Chdir(prepareImageDir)
c.Assert(err, check.IsNil)
defer func() { os.Chdir(cwd) }()

s.testWriteResolvedContent(c, ".")
}

func (s *imageSuite) testWriteResolvedContent(c *check.C, prepareImageDir string) {
// on uc20 there is a "system-seed" under the <PrepareImageDir>
uc20systemSeed, err := filepath.Abs(filepath.Join(prepareImageDir, "system-seed"))
c.Assert(err, check.IsNil)
err = os.MkdirAll(uc20systemSeed, 0755)
c.Assert(err, check.IsNil)

// the resolved content is written here
gadgetRoot := c.MkDir()
snaptest.PopulateDir(gadgetRoot, [][]string{
{"meta/snap.yaml", packageGadget},
{"meta/gadget.yaml", validGadgetYaml},
{"system-seed.efi", "content of system-seed.efi"},
{"grubx64.efi", "content of grubx64.efi"},
{"foo", "content of foo"},
{"non-fs.img", "content of non-fs.img"},
})
kernelRoot := c.MkDir()

model := s.makeUC20Model(nil)
gadgetInfo, err := gadget.ReadInfoAndValidate(gadgetRoot, model, nil)
c.Assert(err, check.IsNil)

err = image.WriteResolvedContent(prepareImageDir, gadgetInfo, gadgetRoot, kernelRoot)
c.Assert(err, check.IsNil)

// XXX: add testutil.DirEquals([][]string)
cmd := exec.Command("find", ".", "-printf", "%y %P\n")
cmd.Dir = prepareImageDir
tree, err := cmd.CombinedOutput()
c.Assert(err, check.IsNil)
c.Check(string(tree), check.Equals, `d
d system-seed
d system-seed/EFI
d system-seed/EFI/boot
f system-seed/EFI/boot/system-seed.efi
d resolved-content
d resolved-content/vol1
d resolved-content/vol1/structure-name
d resolved-content/vol1/structure-name/EFI
d resolved-content/vol1/structure-name/EFI/boot
f resolved-content/vol1/structure-name/EFI/boot/grubx64.efi
l resolved-content/vol1/ubuntu-seed
d resolved-content/vol2
d resolved-content/vol2/struct2
d resolved-content/vol2/struct2/subdir
f resolved-content/vol2/struct2/subdir/foo
`)
// check symlink target for "ubuntu-seed" -> <prepareImageDir>/system-seed
t, err := os.Readlink(filepath.Join(prepareImageDir, "resolved-content/vol1/ubuntu-seed"))
c.Assert(err, check.IsNil)
c.Check(t, check.Equals, uc20systemSeed)
}
33 changes: 23 additions & 10 deletions image/image_linux.go
Expand Up @@ -123,7 +123,7 @@ func decodeModelAssertion(opts *Options) (*asserts.Model, error) {
return modela, nil
}

func unpackGadget(gadgetFname, gadgetUnpackDir string) error {
func unpackSnap(gadgetFname, gadgetUnpackDir string) error {
// FIXME: jumping through layers here, we need to make
// unpack part of the container interface (again)
snap := squashfs.New(gadgetFname)
Expand Down Expand Up @@ -239,12 +239,15 @@ func setupSeed(tsto *ToolingStore, model *asserts.Model, opts *Options) error {
return err
}

var gadgetUnpackDir string
var gadgetUnpackDir, kernelUnpackDir string
// create directory for later unpacking the gadget in
if !opts.Classic {
gadgetUnpackDir = filepath.Join(opts.PrepareDir, "gadget")
if err := os.MkdirAll(gadgetUnpackDir, 0755); err != nil {
return fmt.Errorf("cannot create gadget unpack dir %q: %s", gadgetUnpackDir, err)
kernelUnpackDir = filepath.Join(opts.PrepareDir, "kernel")
for _, unpackDir := range []string{gadgetUnpackDir, kernelUnpackDir} {
if err := os.MkdirAll(unpackDir, 0755); err != nil {
return fmt.Errorf("cannot create unpack dir %q: %s", unpackDir, err)
}
}
}

Expand Down Expand Up @@ -396,6 +399,7 @@ func setupSeed(tsto *ToolingStore, model *asserts.Model, opts *Options) error {
// find the snap.Info/path for kernel/os/base so
// that boot.MakeBootable can DTRT
gadgetFname := ""
kernelFname := ""
for _, sn := range bootSnaps {
switch sn.Info.Type() {
case snap.TypeGadget:
Expand All @@ -406,30 +410,39 @@ func setupSeed(tsto *ToolingStore, model *asserts.Model, opts *Options) error {
case snap.TypeKernel:
bootWith.Kernel = sn.Info
bootWith.KernelPath = sn.Path
kernelFname = sn.Path
}
}

// unpacking the gadget for core models
if err := unpackGadget(gadgetFname, gadgetUnpackDir); err != nil {
if err := unpackSnap(gadgetFname, gadgetUnpackDir); err != nil {
return err
}
if err := unpackSnap(kernelFname, kernelUnpackDir); err != nil {
return err
}

if err := boot.MakeBootable(model, bootRootDir, bootWith, nil); err != nil {
return err
}

gadgetInfo, err := gadget.ReadInfoAndValidate(gadgetUnpackDir, model, nil)
if err != nil {
return err
}

// write resolved content to structure root
if err := writeResolvedContent(opts.PrepareDir, gadgetInfo, gadgetUnpackDir, kernelUnpackDir); err != nil {
return err
}

// early config & cloud-init config (done at install for Core 20)
if !core20 {
// and the cloud-init things
if err := installCloudConfig(rootDir, gadgetUnpackDir); err != nil {
return err
}

gadgetInfo, err := gadget.ReadInfoAndValidate(gadgetUnpackDir, model, nil)
if err != nil {
return err
}

defaultsDir := sysconfig.WritableDefaultsDir(rootDir)
defaults := gadget.SystemDefaults(gadgetInfo.Defaults)
if len(defaults) > 0 {
Expand Down