From fafbfcc20aa9b0f9a498e20b6e8d78b6ae851732 Mon Sep 17 00:00:00 2001 From: Andrew Phelps Date: Mon, 16 Dec 2024 15:00:27 -0500 Subject: [PATCH 1/3] daemon: remove .snap suffix given to uploaded containers --- daemon/api_sideload_n_try.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/api_sideload_n_try.go b/daemon/api_sideload_n_try.go index 47b895aba89..72d5c718b89 100644 --- a/daemon/api_sideload_n_try.go +++ b/daemon/api_sideload_n_try.go @@ -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) } From 2dc8887a77250cb98ea3864eacbd1526d5cef5ef Mon Sep 17 00:00:00 2001 From: Andrew Phelps Date: Mon, 16 Dec 2024 14:51:34 -0500 Subject: [PATCH 2/3] s/seedwriter: allow using snaps and components with improper file extensions with IgnoreOptionFileExtentions flag --- seed/seedwriter/writer.go | 12 ++++--- seed/seedwriter/writer_test.go | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/seed/seedwriter/writer.go b/seed/seedwriter/writer.go index c84abd27c02..2548baf7f3c 100644 --- a/seed/seedwriter/writer.go +++ b/seed/seedwriter/writer.go @@ -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 @@ -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", @@ -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) { @@ -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) { @@ -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 } } diff --git a/seed/seedwriter/writer_test.go b/seed/seedwriter/writer_test.go index b34443e4012..74389285a37 100644 --- a/seed/seedwriter/writer_test.go +++ b/seed/seedwriter/writer_test.go @@ -27,6 +27,7 @@ import ( "path" "path/filepath" "sort" + "strings" "testing" "time" @@ -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", From 94c6766e69475da157f1c16ed71d33704833ad62 Mon Sep 17 00:00:00 2001 From: Andrew Phelps Date: Fri, 20 Dec 2024 10:40:58 -0500 Subject: [PATCH 3/3] o/devicestate: ignore file extensions when creating seed from validated snaps --- overlord/devicestate/devicestate_systems_test.go | 12 +++++++++++- overlord/devicestate/systems.go | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/overlord/devicestate/devicestate_systems_test.go b/overlord/devicestate/devicestate_systems_test.go index 82b6ca523ba..38d145e1962 100644 --- a/overlord/devicestate/devicestate_systems_test.go +++ b/overlord/devicestate/devicestate_systems_test.go @@ -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 { @@ -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, diff --git a/overlord/devicestate/systems.go b/overlord/devicestate/systems.go index 9926a607da0..b57aedad8a9 100644 --- a/overlord/devicestate/systems.go +++ b/overlord/devicestate/systems.go @@ -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 {