diff --git a/internal/pkg/extensions/compress.go b/internal/pkg/extensions/compress.go index e2c7dde504..0988ce9cfe 100644 --- a/internal/pkg/extensions/compress.go +++ b/internal/pkg/extensions/compress.go @@ -45,14 +45,14 @@ func (ext *Extension) Compress(squashPath, initramfsPath string, quirks quirks.Q } for _, path := range initramfsPaths { - if _, err := os.Stat(filepath.Join(ext.rootfsPath, path)); err == nil { - if err = moveFiles(filepath.Join(ext.rootfsPath, path), filepath.Join(initramfsPath, path)); err != nil { + if _, err := os.Stat(filepath.Join(ext.RootfsPath(), path)); err == nil { + if err = moveFiles(filepath.Join(ext.RootfsPath(), path), filepath.Join(initramfsPath, path)); err != nil { return "", err } } } - squashPath = filepath.Join(squashPath, fmt.Sprintf("%s.sqsh", ext.directory)) + squashPath = filepath.Join(squashPath, fmt.Sprintf("%s.sqsh", ext.Directory())) var compressArgs []string @@ -62,7 +62,7 @@ func (ext *Extension) Compress(squashPath, initramfsPath string, quirks quirks.Q compressArgs = []string{"-comp", "xz", "-Xdict-size", "100%"} } - cmd := exec.Command("mksquashfs", append([]string{ext.rootfsPath, squashPath, "-all-root", "-noappend", "-no-progress"}, compressArgs...)...) + cmd := exec.Command("mksquashfs", append([]string{ext.RootfsPath(), squashPath, "-all-root", "-noappend", "-no-progress"}, compressArgs...)...) cmd.Stderr = os.Stderr return squashPath, cmd.Run() @@ -89,7 +89,7 @@ func appendBlob(dst io.Writer, srcPath string) error { func (ext *Extension) handleUcode(initramfsPath string) error { for _, ucode := range earlyCPUUcode { - matches, err := filepath.Glob(filepath.Join(ext.rootfsPath, ucode.glob)) + matches, err := filepath.Glob(filepath.Join(ext.RootfsPath(), ucode.glob)) if err != nil { return err } diff --git a/internal/pkg/extensions/extensions.go b/internal/pkg/extensions/extensions.go index 690037892d..785d86f95c 100644 --- a/internal/pkg/extensions/extensions.go +++ b/internal/pkg/extensions/extensions.go @@ -6,28 +6,10 @@ package extensions import ( - "path/filepath" - "github.com/siderolabs/talos/pkg/machinery/extensions" ) -// Extension represents unpacked extension in the filesystem. +// Extension wraps the extensions.Extension type with additional methods. type Extension struct { - Manifest extensions.Manifest - - directory string - rootfsPath string -} - -func newExtension(rootfsPath, directory string) *Extension { - extension := &Extension{ - rootfsPath: rootfsPath, - directory: directory, - } - - if extension.directory == "" { - extension.directory = filepath.Base(rootfsPath) - } - - return extension + *extensions.Extension } diff --git a/internal/pkg/extensions/extensions_test.go b/internal/pkg/extensions/extensions_test.go index fdf362636e..5d17db0773 100644 --- a/internal/pkg/extensions/extensions_test.go +++ b/internal/pkg/extensions/extensions_test.go @@ -14,35 +14,21 @@ import ( "github.com/siderolabs/talos/internal/pkg/extensions" "github.com/siderolabs/talos/pkg/machinery/imager/quirks" - "github.com/siderolabs/talos/pkg/machinery/version" ) -func TestLoadValidate(t *testing.T) { - ext, err := extensions.Load("testdata/good/extension1") - require.NoError(t, err) - - assert.Equal(t, "gvisor", ext.Manifest.Metadata.Name) - - // override Talos version to make it predictable - oldVersion := version.Tag - version.Tag = "v1.0.0" - - t.Cleanup(func() { - version.Tag = oldVersion - }) - - assert.NoError(t, ext.Validate()) -} - func TestCompress(t *testing.T) { // Compress is going to change contents of the extension, copy to some temporary directory extDir := t.TempDir() require.NoError(t, exec.Command("cp", "-r", "testdata/good/extension1", extDir).Run()) - ext, err := extensions.Load(filepath.Join(extDir, "extension1")) + exts, err := extensions.List(extDir) require.NoError(t, err) + require.Len(t, exts, 1) + + ext := exts[0] + squashDest, initramfsDest := t.TempDir(), t.TempDir() squashFile, err := ext.Compress(squashDest, initramfsDest, quirks.New("")) assert.NoError(t, err) @@ -50,51 +36,3 @@ func TestCompress(t *testing.T) { assert.FileExists(t, squashFile) assert.FileExists(t, filepath.Join(initramfsDest, "lib", "firmware", "amd", "cpu")) } - -func TestValidateFailures(t *testing.T) { - // override Talos version to make it predictable - oldVersion := version.Tag - version.Tag = "v1.0.0" - - t.Cleanup(func() { - version.Tag = oldVersion - }) - - for _, tt := range []struct { - name string - loadError string - validateError string - }{ - { - name: "wrongfiles", - loadError: "unexpected file \"a\"", - }, - { - name: "emptymanifest", - loadError: "unsupported manifest version: \"\"", - }, - { - name: "norootfs", - loadError: "extension rootfs is missing", - }, - { - name: "badpaths", - validateError: "path \"/boot/vmlinuz\" is not allowed in extensions", - }, - } { - t.Run(tt.name, func(t *testing.T) { - ext, err := extensions.Load(filepath.Join("testdata/bad", tt.name)) - - if tt.loadError == "" { - require.NoError(t, err) - } else { - assert.EqualError(t, err, tt.loadError) - } - - if err == nil { - err = ext.Validate() - assert.EqualError(t, err, tt.validateError) - } - }) - } -} diff --git a/internal/pkg/extensions/kernel_modules.go b/internal/pkg/extensions/kernel_modules.go index 3afa57e226..80e78f218d 100644 --- a/internal/pkg/extensions/kernel_modules.go +++ b/internal/pkg/extensions/kernel_modules.go @@ -35,7 +35,7 @@ func (ext *Extension) ProvidesKernelModules() bool { // KernelModuleDirectory returns the path to the kernel modules directory. func (ext *Extension) KernelModuleDirectory() string { - return filepath.Join(ext.rootfsPath, constants.KernelModulesPath) + return filepath.Join(ext.RootfsPath(), constants.KernelModulesPath) } func autoDecompress(r io.Reader) (io.Reader, error) { @@ -142,18 +142,20 @@ func GenerateKernelModuleDependencyTreeExtension(extensionPathsWithKernelModules return nil, err } - kernelModulesDepTreeExtension := newExtension(kernelModulesDependencyTreeStagingDir, "modules.dep") - kernelModulesDepTreeExtension.Manifest = extensions.Manifest{ - Version: kernelVersionPath, - Metadata: extensions.Metadata{ - Name: "modules.dep", - Version: kernelVersionPath, - Author: "Talos Machinery", - Description: "Combined modules.dep for all extensions", + kernelModulesDepTreeExtension := extensions.New( + kernelModulesDependencyTreeStagingDir, "modules.dep", + extensions.Manifest{ + Version: kernelVersionPath, + Metadata: extensions.Metadata{ + Name: "modules.dep", + Version: kernelVersionPath, + Author: "Talos Machinery", + Description: "Combined modules.dep for all extensions", + }, }, - } + ) - return kernelModulesDepTreeExtension, nil + return &Extension{kernelModulesDepTreeExtension}, nil } func logErr(msg string, f func() error) { diff --git a/internal/pkg/extensions/list.go b/internal/pkg/extensions/list.go index d4a0377061..897be05d73 100644 --- a/internal/pkg/extensions/list.go +++ b/internal/pkg/extensions/list.go @@ -9,6 +9,8 @@ import ( "os" "path/filepath" "sort" + + "github.com/siderolabs/talos/pkg/machinery/extensions" ) // List prepared unpacked extensions under rootPath. @@ -35,12 +37,12 @@ func List(rootPath string) ([]*Extension, error) { return nil, fmt.Errorf("unexpected non-directory entry: %q", item.Name()) } - ext, err := Load(filepath.Join(rootPath, item.Name())) + ext, err := extensions.Load(filepath.Join(rootPath, item.Name())) if err != nil { return nil, fmt.Errorf("error loading extension %s: %w", item.Name(), err) } - result = append(result, ext) + result = append(result, &Extension{ext}) } return result, nil diff --git a/pkg/machinery/extensions/extensions.go b/pkg/machinery/extensions/extensions.go index 6c9dbbf320..3aebc0a9b9 100644 --- a/pkg/machinery/extensions/extensions.go +++ b/pkg/machinery/extensions/extensions.go @@ -5,6 +5,8 @@ // Package extensions contains Talos extensions specific API. package extensions +import "path/filepath" + // AllowedPaths lists paths allowed in the extension images. var AllowedPaths = []string{ "/etc/cri/conf.d", @@ -18,3 +20,37 @@ var AllowedPaths = []string{ "/usr/share/egl", "/etc/vulkan", } + +// Extension represents unpacked extension in the filesystem. +type Extension struct { + Manifest Manifest + + directory string + rootfsPath string +} + +// RootfsPath returns the path to the rootfs directory. +func (ext *Extension) RootfsPath() string { + return ext.rootfsPath +} + +// Directory returns the directory name of the extension. +func (ext *Extension) Directory() string { + return ext.directory +} + +// New creates a new extension from the rootfs path, directory name and manifest. +func New(rootfsPath, directory string, manifest Manifest) *Extension { + extension := &Extension{ + Manifest: manifest, + + rootfsPath: rootfsPath, + directory: directory, + } + + if extension.directory == "" { + extension.directory = filepath.Base(rootfsPath) + } + + return extension +} diff --git a/pkg/machinery/extensions/extensions_test.go b/pkg/machinery/extensions/extensions_test.go new file mode 100644 index 0000000000..576d0926c5 --- /dev/null +++ b/pkg/machinery/extensions/extensions_test.go @@ -0,0 +1,81 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package extensions_test + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/pkg/machinery/extensions" + "github.com/siderolabs/talos/pkg/machinery/version" +) + +func TestLoadValidate(t *testing.T) { + ext, err := extensions.Load("testdata/good/extension1") + require.NoError(t, err) + + assert.Equal(t, "gvisor", ext.Manifest.Metadata.Name) + + // override Talos version to make it predictable + oldVersion := version.Tag + version.Tag = "v1.0.0" + + t.Cleanup(func() { + version.Tag = oldVersion + }) + + assert.NoError(t, ext.Validate()) +} + +func TestValidateFailures(t *testing.T) { + // override Talos version to make it predictable + oldVersion := version.Tag + version.Tag = "v1.0.0" + + t.Cleanup(func() { + version.Tag = oldVersion + }) + + for _, tt := range []struct { + name string + loadError string + validateError string + }{ + { + name: "wrongfiles", + loadError: "unexpected file \"a\"", + }, + { + name: "emptymanifest", + loadError: "unsupported manifest version: \"\"", + }, + { + name: "norootfs", + loadError: "extension rootfs is missing", + }, + { + name: "badpaths", + validateError: "path \"/boot/vmlinuz\" is not allowed in extensions", + }, + } { + t.Run(tt.name, func(t *testing.T) { + ext, err := extensions.Load(filepath.Join("testdata/bad", tt.name)) + + if tt.loadError == "" { + require.NoError(t, err) + } else { + assert.EqualError(t, err, tt.loadError) + } + + if err == nil { + err = ext.Validate() + assert.EqualError(t, err, tt.validateError) + } + }) + } +} diff --git a/internal/pkg/extensions/load.go b/pkg/machinery/extensions/load.go similarity index 94% rename from internal/pkg/extensions/load.go rename to pkg/machinery/extensions/load.go index fe258ff267..b9956d8982 100644 --- a/internal/pkg/extensions/load.go +++ b/pkg/machinery/extensions/load.go @@ -11,8 +11,6 @@ import ( "path/filepath" "gopkg.in/yaml.v3" - - "github.com/siderolabs/talos/pkg/machinery/extensions" ) // Load extension from the filesystem. @@ -41,7 +39,7 @@ func Load(path string) (*Extension, error) { } } - var zeroManifest extensions.Manifest + var zeroManifest Manifest if extension.Manifest == zeroManifest { return nil, errors.New("extension manifest is missing") diff --git a/internal/pkg/extensions/testdata/bad/badpaths/manifest.yaml b/pkg/machinery/extensions/testdata/bad/badpaths/manifest.yaml similarity index 100% rename from internal/pkg/extensions/testdata/bad/badpaths/manifest.yaml rename to pkg/machinery/extensions/testdata/bad/badpaths/manifest.yaml diff --git a/internal/pkg/extensions/testdata/bad/badpaths/rootfs/boot/vmlinuz b/pkg/machinery/extensions/testdata/bad/badpaths/rootfs/boot/vmlinuz similarity index 100% rename from internal/pkg/extensions/testdata/bad/badpaths/rootfs/boot/vmlinuz rename to pkg/machinery/extensions/testdata/bad/badpaths/rootfs/boot/vmlinuz diff --git a/internal/pkg/extensions/testdata/bad/emptymanifest/manifest.yaml b/pkg/machinery/extensions/testdata/bad/emptymanifest/manifest.yaml similarity index 100% rename from internal/pkg/extensions/testdata/bad/emptymanifest/manifest.yaml rename to pkg/machinery/extensions/testdata/bad/emptymanifest/manifest.yaml diff --git a/internal/pkg/extensions/testdata/bad/norootfs/manifest.yaml b/pkg/machinery/extensions/testdata/bad/norootfs/manifest.yaml similarity index 100% rename from internal/pkg/extensions/testdata/bad/norootfs/manifest.yaml rename to pkg/machinery/extensions/testdata/bad/norootfs/manifest.yaml diff --git a/internal/pkg/extensions/testdata/bad/wrongfiles/a b/pkg/machinery/extensions/testdata/bad/wrongfiles/a similarity index 100% rename from internal/pkg/extensions/testdata/bad/wrongfiles/a rename to pkg/machinery/extensions/testdata/bad/wrongfiles/a diff --git a/pkg/machinery/extensions/testdata/good/extension1/manifest.yaml b/pkg/machinery/extensions/testdata/good/extension1/manifest.yaml new file mode 100644 index 0000000000..45bb96b97a --- /dev/null +++ b/pkg/machinery/extensions/testdata/good/extension1/manifest.yaml @@ -0,0 +1,10 @@ +version: v1alpha1 +metadata: + name: gvisor + version: 20220117.0-v1.0.0 + author: Andrew Rynhard + description: > + This system extension provides gVisor using containerd's runtime handler. + compatibility: + talos: + version: ">= v1.0.0" diff --git a/pkg/machinery/extensions/testdata/good/extension1/rootfs/lib/firmware/amd/cpu b/pkg/machinery/extensions/testdata/good/extension1/rootfs/lib/firmware/amd/cpu new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/machinery/extensions/testdata/good/extension1/rootfs/lib64/ld-linux-x86-64.so.2 b/pkg/machinery/extensions/testdata/good/extension1/rootfs/lib64/ld-linux-x86-64.so.2 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/machinery/extensions/testdata/good/extension1/rootfs/usr/local/lib/a.so b/pkg/machinery/extensions/testdata/good/extension1/rootfs/usr/local/lib/a.so new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/machinery/extensions/testdata/good/extension1/rootfs/usr/local/lib/a.so.1 b/pkg/machinery/extensions/testdata/good/extension1/rootfs/usr/local/lib/a.so.1 new file mode 120000 index 0000000000..534807a15a --- /dev/null +++ b/pkg/machinery/extensions/testdata/good/extension1/rootfs/usr/local/lib/a.so.1 @@ -0,0 +1 @@ +a.so \ No newline at end of file diff --git a/internal/pkg/extensions/validate.go b/pkg/machinery/extensions/validate.go similarity index 95% rename from internal/pkg/extensions/validate.go rename to pkg/machinery/extensions/validate.go index ed5e569665..118b397c9b 100644 --- a/internal/pkg/extensions/validate.go +++ b/pkg/machinery/extensions/validate.go @@ -13,7 +13,6 @@ import ( "github.com/blang/semver/v4" - "github.com/siderolabs/talos/pkg/machinery/extensions" "github.com/siderolabs/talos/pkg/machinery/version" ) @@ -95,7 +94,7 @@ func (ext *Extension) validateContents() error { if !d.IsDir() { allowed := false - for _, allowedPath := range extensions.AllowedPaths { + for _, allowedPath := range AllowedPaths { if strings.HasPrefix(itemPath, allowedPath) { _, err = filepath.Rel(allowedPath, itemPath) if err == nil {