Skip to content

Commit

Permalink
s/seedwriter: allow using snaps and components with improper file ex…
Browse files Browse the repository at this point in the history
…tensions with IgnoreOptionFileExtentions flag (#14891)

* daemon: remove .snap suffix given to uploaded containers

* s/seedwriter: allow using snaps and components with improper file extensions with IgnoreOptionFileExtentions flag

* o/devicestate: ignore file extensions when creating seed from validated snaps
  • Loading branch information
andrewphelpsj authored Jan 6, 2025
1 parent 192ed07 commit 0fd760d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 6 deletions.
2 changes: 1 addition & 1 deletion daemon/api_sideload_n_try.go
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ func readForm(reader *multipart.Reader) (_ *Form, apiErr *apiError) {
// its path. If the path is not empty then a file was written and it's the
// caller's responsibility to clean it up (even if the error is non-nil).
func writeToTempFile(reader io.Reader) (path string, err error) {
tmpf, err := os.CreateTemp(dirs.SnapBlobDir, dirs.LocalInstallBlobTempPrefix+"*.snap")
tmpf, err := os.CreateTemp(dirs.SnapBlobDir, dirs.LocalInstallBlobTempPrefix+"*")
if err != nil {
return "", fmt.Errorf("cannot create temp file for form data file part: %v", err)
}
Expand Down
12 changes: 11 additions & 1 deletion overlord/devicestate/devicestate_systems_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3833,7 +3833,6 @@ func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemValid

localSnaps := make([]devicestate.LocalSnap, 0, len(snapRevisions))
for name, rev := range snapRevisions {

var files [][]string
var base string
if snapTypes[name] == snap.TypeGadget {
Expand All @@ -3845,6 +3844,17 @@ func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemValid

si, path := createLocalSnap(c, name, fakeSnapID(name), rev.N, string(snapTypes[name]), base, files)

// when we're creating a recovery system from snaps that are uploaded,
// they get written to disk as tmp files. these don't have a .snap file
// extension. this emulates that behavior.
//
// here we make sure that the seed writer allows us to create a seed
// from snaps with invalid/missing file extensions.
trimmed := strings.TrimSuffix(path, ".snap")
err := os.Rename(path, trimmed)
c.Assert(err, IsNil)
path = trimmed

localSnaps = append(localSnaps, devicestate.LocalSnap{
SideInfo: si,
Path: path,
Expand Down
5 changes: 5 additions & 0 deletions overlord/devicestate/systems.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ func createSystemForModelFromValidatedSnaps(
// RW mount of ubuntu-seed
SeedDir: boot.InitramfsUbuntuSeedDir,
Label: label,

// due to the way that temp files are handled in daemon, they do not
// have .snap or .comp extensions. this flag lets us ignore that
// requirement.
IgnoreOptionFileExtentions: true,
}
w, err := seedwriter.New(model, wOpts)
if err != nil {
Expand Down
12 changes: 8 additions & 4 deletions seed/seedwriter/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type Options struct {
// ManifestPath if set, specifies the file path where the
// seed.manifest file should be written.
ManifestPath string

// IgnoreOptionFileExtentions if set, snaps and components will not be
// required to end in .snap or .comp, respectively.
IgnoreOptionFileExtentions bool
}

// manifest returns either the manifest already provided by the
Expand Down Expand Up @@ -417,7 +421,7 @@ func (w *Writer) warningf(format string, a ...interface{}) {
w.warnings = append(w.warnings, fmt.Sprintf(format, a...))
}

func validateComponent(optComp *OptionsComponent) error {
func (w *Writer) validateComponent(optComp *OptionsComponent) error {
if optComp.Name != "" {
if optComp.Path != "" {
return fmt.Errorf("cannot specify both name and path for component %q",
Expand All @@ -427,7 +431,7 @@ func validateComponent(optComp *OptionsComponent) error {
return err
}
} else {
if !strings.HasSuffix(optComp.Path, ".comp") {
if !strings.HasSuffix(optComp.Path, ".comp") && !w.opts.IgnoreOptionFileExtentions {
return fmt.Errorf("local option component %q does not end in .comp", optComp.Path)
}
if !osutil.FileExists(optComp.Path) {
Expand Down Expand Up @@ -469,7 +473,7 @@ func (w *Writer) SetOptionsSnaps(optSnaps []*OptionsSnap) error {
}
w.byNameOptSnaps.Add(sn)
} else {
if !strings.HasSuffix(sn.Path, ".snap") {
if !strings.HasSuffix(sn.Path, ".snap") && !w.opts.IgnoreOptionFileExtentions {
return fmt.Errorf("local option snap %q does not end in .snap", sn.Path)
}
if !osutil.FileExists(sn.Path) {
Expand All @@ -489,7 +493,7 @@ func (w *Writer) SetOptionsSnaps(optSnaps []*OptionsSnap) error {
}
}
for _, comp := range sn.Components {
if err := validateComponent(&comp); err != nil {
if err := w.validateComponent(&comp); err != nil {
return err
}
}
Expand Down
57 changes: 57 additions & 0 deletions seed/seedwriter/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"path"
"path/filepath"
"sort"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -330,6 +331,62 @@ func (s writerSuite) TestSetOptionsSnapsErrors(c *C) {
}
}

func (s writerSuite) TestSetOptionsSnapsIgnoreExtensions(c *C) {
model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"display-name": "my model",
"architecture": "amd64",
"store": "my-store",
"base": "core20",
"grade": "dangerous",
"snaps": []interface{}{
map[string]interface{}{
"name": "pc-kernel",
"id": s.AssertedSnapID("pc-kernel"),
"type": "kernel",
"default-channel": "20",
},
map[string]interface{}{
"name": "pc",
"id": s.AssertedSnapID("pc"),
"type": "gadget",
"default-channel": "20",
},
map[string]interface{}{
"name": "required20",
"id": s.AssertedSnapID("required20"),
},
},
})

s.opts.Label = "20240714"
s.opts.IgnoreOptionFileExtentions = true
w, err := seedwriter.New(model, s.opts)
c.Assert(err, IsNil)

snapPath := s.makeLocalSnap(c, "required20")
compPath := s.makeLocalComponent(c, "required20+comp1")

trimmed := strings.TrimSuffix(snapPath, ".snap")
os.Rename(snapPath, trimmed)
snapPath = trimmed

trimmed = strings.TrimSuffix(compPath, ".comp")
os.Rename(compPath, trimmed)
compPath = trimmed

err = w.SetOptionsSnaps([]*seedwriter.OptionsSnap{
{
Path: snapPath,
Components: []seedwriter.OptionsComponent{
{
Path: compPath,
},
},
},
})
c.Assert(err, IsNil)
}

func (s *writerSuite) TestSnapsToDownloadCore16(c *C) {
model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"display-name": "my model",
Expand Down

0 comments on commit 0fd760d

Please sign in to comment.