mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-09-12 12:36:56 +00:00
We were basically overwriting the file if it existed which is bad. Now it will read the file if it exists and ingest the existing ones, then in will override witht he given vars if they match, warn the user and then store the full processed vars. This means it will never overwrite any vars again Signed-off-by: Itxaka <itxaka@kairos.io>
931 lines
33 KiB
Go
931 lines
33 KiB
Go
/*
|
|
Copyright © 2021 SUSE LLC
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package elemental_test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/diskfs/go-diskfs"
|
|
"golang.org/x/sys/unix"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
sc "syscall"
|
|
"testing"
|
|
|
|
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
|
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/elemental"
|
|
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
|
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
|
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
|
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
|
|
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
|
|
|
fileBackend "github.com/diskfs/go-diskfs/backend/file"
|
|
"github.com/diskfs/go-diskfs/partition/gpt"
|
|
"github.com/gofrs/uuid"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/sanity-io/litter"
|
|
"github.com/twpayne/go-vfs/v5/vfst"
|
|
)
|
|
|
|
func TestElementalSuite(t *testing.T) {
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, "Elemental test suite")
|
|
}
|
|
|
|
var _ = Describe("Elemental", Label("elemental"), func() {
|
|
var config *agentConfig.Config
|
|
var runner *v1mock.FakeRunner
|
|
var logger sdkTypes.KairosLogger
|
|
var syscall *v1mock.FakeSyscall
|
|
var cl *v1mock.FakeHTTPClient
|
|
var mounter *v1mock.ErrorMounter
|
|
var fs *vfst.TestFS
|
|
var cleanup func()
|
|
var extractor *v1mock.FakeImageExtractor
|
|
var memLog *bytes.Buffer
|
|
var devLoopInt int
|
|
|
|
BeforeEach(func() {
|
|
memLog = &bytes.Buffer{}
|
|
logger = sdkTypes.NewBufferLogger(memLog)
|
|
logger.SetLevel("debug")
|
|
runner = v1mock.NewFakeRunner()
|
|
syscall = &v1mock.FakeSyscall{}
|
|
devLoopInt = 44
|
|
syscall.SideEffectSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err sc.Errno) {
|
|
// Trap the call for getting a free loop device number
|
|
if trap == sc.SYS_IOCTL && a2 == unix.LOOP_CTL_GET_FREE {
|
|
// This is a "get free loop device" syscall
|
|
// We return 44 so it gets the /dev/loop44 because we are cool like that
|
|
// Also we can check below that indeed it set that device as expected
|
|
return uintptr(devLoopInt), 0, sc.Errno(syscall.ReturnValue)
|
|
}
|
|
return 0, 0, sc.Errno(syscall.ReturnValue)
|
|
}
|
|
mounter = v1mock.NewErrorMounter()
|
|
cl = &v1mock.FakeHTTPClient{}
|
|
fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{
|
|
"/dev/loop-control": "",
|
|
fmt.Sprintf("/dev/loop%d", devLoopInt): "",
|
|
})
|
|
extractor = v1mock.NewFakeImageExtractor(logger)
|
|
config = agentConfig.NewConfig(
|
|
agentConfig.WithFs(fs),
|
|
agentConfig.WithRunner(runner),
|
|
agentConfig.WithLogger(logger),
|
|
agentConfig.WithMounter(mounter),
|
|
agentConfig.WithSyscall(syscall),
|
|
agentConfig.WithClient(cl),
|
|
agentConfig.WithImageExtractor(extractor),
|
|
)
|
|
})
|
|
AfterEach(func() { cleanup() })
|
|
Describe("MountRWPartition", Label("mount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
spec := &v1.InstallSpec{}
|
|
parts = agentConfig.NewInstallElementalPartitions(logger, spec)
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
parts.OEM.Path = "/dev/device1"
|
|
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
It("Mounts and umounts a partition with RW", func() {
|
|
umount, err := el.MountRWPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
Expect(lst[0].Opts).To(Equal([]string{"rw"}))
|
|
|
|
Expect(umount()).ShouldNot(HaveOccurred())
|
|
lst, _ = mounter.List()
|
|
Expect(len(lst)).To(Equal(0))
|
|
})
|
|
It("Remounts a partition with RW", func() {
|
|
err := el.MountPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
|
|
umount, err := el.MountRWPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ = mounter.List()
|
|
// fake mounter is not merging remounts it just appends
|
|
Expect(len(lst)).To(Equal(2))
|
|
Expect(lst[1].Opts).To(Equal([]string{"remount", "rw"}))
|
|
|
|
Expect(umount()).ShouldNot(HaveOccurred())
|
|
lst, _ = mounter.List()
|
|
// Increased once more to remount read-onply
|
|
Expect(len(lst)).To(Equal(3))
|
|
Expect(lst[2].Opts).To(Equal([]string{"remount", "ro"}))
|
|
})
|
|
It("Fails to mount a partition", func() {
|
|
mounter.ErrorOnMount = true
|
|
_, err := el.MountRWPartition(parts.OEM)
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
It("Fails to remount a partition", func() {
|
|
err := el.MountPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
|
|
mounter.ErrorOnMount = true
|
|
_, err = el.MountRWPartition(parts.OEM)
|
|
Expect(err).Should(HaveOccurred())
|
|
lst, _ = mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
})
|
|
})
|
|
Describe("MountPartitions", Label("MountPartitions", "disk", "partition", "mount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
spec := &v1.InstallSpec{}
|
|
parts = agentConfig.NewInstallElementalPartitions(logger, spec)
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
parts.OEM.Path = "/dev/device2"
|
|
parts.Recovery.Path = "/dev/device3"
|
|
parts.State.Path = "/dev/device4"
|
|
parts.Persistent.Path = "/dev/device5"
|
|
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
It("Mounts disk partitions", func() {
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(4))
|
|
})
|
|
|
|
It("Mounts disk partitions excluding recovery", func() {
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false, parts.Recovery))
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
for _, i := range lst {
|
|
Expect(i.Path).NotTo(Equal("/dev/device3"))
|
|
}
|
|
})
|
|
|
|
It("Fails if some partition resists to mount ", func() {
|
|
mounter.ErrorOnMount = true
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
|
|
It("Fails if oem partition is not found ", func() {
|
|
parts.OEM.Path = ""
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("UnmountPartitions", Label("UnmountPartitions", "disk", "partition", "unmount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
spec := &v1.InstallSpec{}
|
|
parts = agentConfig.NewInstallElementalPartitions(logger, spec)
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
parts.OEM.Path = "/dev/device2"
|
|
parts.Recovery.Path = "/dev/device3"
|
|
parts.State.Path = "/dev/device4"
|
|
parts.Persistent.Path = "/dev/device5"
|
|
|
|
el = elemental.NewElemental(config)
|
|
err = el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("Unmounts disk partitions", func() {
|
|
err := el.UnmountPartitions(parts.PartitionsByMountPoint(true))
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(0))
|
|
})
|
|
|
|
It("Fails to unmount disk partitions", func() {
|
|
mounter.ErrorOnUnmount = true
|
|
err := el.UnmountPartitions(parts.PartitionsByMountPoint(true))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("MountImage", Label("MountImage", "mount", "image"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
BeforeEach(func() {
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{MountPoint: "/some/mountpoint", File: "/image.file"}
|
|
Expect(fs.WriteFile("/image.file", []byte{}, cnst.FilePerm)).To(Succeed())
|
|
})
|
|
|
|
It("Mounts file system image", func() {
|
|
err := el.MountImage(img)
|
|
Expect(err).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(fmt.Sprintf("/dev/loop%d", devLoopInt)), litter.Sdump(img))
|
|
})
|
|
|
|
It("Fails to set a loop device", Label("loop"), func() {
|
|
// Return error on syscall call
|
|
syscall.ReturnValue = 10
|
|
Expect(el.MountImage(img)).NotTo(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
|
|
It("Fails to mount a loop device", Label("loop"), func() {
|
|
mounter.ErrorOnMount = true
|
|
Expect(el.MountImage(img)).NotTo(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
})
|
|
Describe("UnmountImage", Label("UnmountImage", "mount", "image"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
BeforeEach(func() {
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{MountPoint: "/some/mountpoint", File: "/image.file"}
|
|
Expect(fs.WriteFile("/image.file", []byte{}, cnst.FilePerm)).To(Succeed())
|
|
Expect(el.MountImage(img)).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(fmt.Sprintf("/dev/loop%d", devLoopInt)))
|
|
})
|
|
|
|
It("Unmounts file system image", func() {
|
|
Expect(el.UnmountImage(img)).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
|
|
It("Fails to unmount a mountpoint", func() {
|
|
mounter.ErrorOnUnmount = true
|
|
Expect(el.UnmountImage(img)).NotTo(BeNil())
|
|
})
|
|
|
|
It("Fails to unset a loop device", Label("loop"), func() {
|
|
syscall.ReturnValue = 10
|
|
Expect(el.UnmountImage(img)).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("CreateFileSystemImage", Label("CreateFileSystemImage", "image"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
BeforeEach(func() {
|
|
img = &v1.Image{
|
|
Label: cnst.ActiveLabel,
|
|
Size: 32,
|
|
File: filepath.Join(cnst.StateDir, "cOS", cnst.ActiveImgFile),
|
|
FS: cnst.LinuxImgFs,
|
|
MountPoint: cnst.ActiveDir,
|
|
Source: v1.NewDirSrc(cnst.IsoBaseTree),
|
|
}
|
|
_ = fsutils.MkdirAll(fs, cnst.IsoBaseTree, cnst.DirPerm)
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
It("Creates a new file system image", func() {
|
|
_, err := fs.Stat(img.File)
|
|
Expect(err).NotTo(BeNil())
|
|
err = el.CreateFileSystemImage(img)
|
|
Expect(err).To(BeNil())
|
|
stat, err := fs.Stat(img.File)
|
|
Expect(err).To(BeNil())
|
|
Expect(stat.Size()).To(Equal(int64(32 * 1024 * 1024)))
|
|
})
|
|
|
|
It("Fails formatting a file system image", Label("format"), func() {
|
|
runner.ReturnError = errors.New("run error")
|
|
_, err := fs.Stat(img.File)
|
|
Expect(err).NotTo(BeNil())
|
|
err = el.CreateFileSystemImage(img)
|
|
Expect(err).NotTo(BeNil())
|
|
_, err = fs.Stat(img.File)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("FormatPartition", Label("FormatPartition", "partition", "format"), func() {
|
|
It("Reformats an already existing partition", func() {
|
|
el := elemental.NewElemental(config)
|
|
part := &sdkTypes.Partition{
|
|
Path: "/dev/device1",
|
|
FS: "ext4",
|
|
FilesystemLabel: "MY_LABEL",
|
|
}
|
|
Expect(el.FormatPartition(part)).To(BeNil())
|
|
})
|
|
|
|
})
|
|
Describe("PartitionAndFormatDevice", Label("PartitionAndFormatDevice", "partition", "format"), func() {
|
|
var cInit *v1mock.FakeCloudInitRunner
|
|
var install *v1.InstallSpec
|
|
var err error
|
|
var el *elemental.Elemental
|
|
var tmpDir string
|
|
|
|
BeforeEach(func() {
|
|
cInit = &v1mock.FakeCloudInitRunner{ExecStages: []string{}, Error: false}
|
|
config.CloudInitRunner = cInit
|
|
tmpDir, err = os.MkdirTemp("", "elements-*")
|
|
Expect(err).To(BeNil())
|
|
Expect(os.RemoveAll(filepath.Join(tmpDir, "/test.img"))).ToNot(HaveOccurred())
|
|
// at least 2Gb in size as state is set to 1G
|
|
_, err = fileBackend.CreateFromPath(filepath.Join(tmpDir, "/test.img"), 2*1024*1024*1024)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
config.Install.Device = filepath.Join(tmpDir, "/test.img")
|
|
install, err = agentConfig.NewInstallSpec(config)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
install.Target = filepath.Join(tmpDir, "/test.img")
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(os.RemoveAll(tmpDir)).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("Successfully creates partitions and formats them, EFI boot", func() {
|
|
install.PartTable = v1.GPT
|
|
install.Firmware = v1.EFI
|
|
Expect(install.Partitions.SetFirmwarePartitions(v1.EFI, v1.GPT)).To(BeNil())
|
|
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
|
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
|
|
defer disk.Close()
|
|
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// check that its type GPT
|
|
Expect(table.Type()).To(Equal("gpt"))
|
|
// Expect the disk UUID to be constant
|
|
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
|
// 5 partitions (boot, oem, recovery, state and persistent)
|
|
Expect(len(table.GetPartitions())).To(Equal(5))
|
|
// Cast the boot partition into specific type to check the type and such
|
|
part := table.GetPartitions()[0]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// Should be efi type
|
|
Expect(partition.Type).To(Equal(gpt.EFISystemPartition))
|
|
// should have boot label
|
|
Expect(partition.Name).To(Equal(cnst.EfiPartName))
|
|
// Should have predictable UUID
|
|
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
|
|
// Check the rest have the proper types
|
|
for i := 1; i < len(table.GetPartitions()); i++ {
|
|
part := table.GetPartitions()[i]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// all of them should have the Linux fs type
|
|
Expect(partition.Type).To(Equal(gpt.LinuxFilesystem))
|
|
}
|
|
})
|
|
It("Successfully creates partitions and formats them, BIOS boot", func() {
|
|
install.PartTable = v1.GPT
|
|
install.Firmware = v1.BIOS
|
|
Expect(install.Partitions.SetFirmwarePartitions(v1.BIOS, v1.GPT)).To(BeNil())
|
|
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
|
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
|
|
defer disk.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// check that its type GPT
|
|
Expect(table.Type()).To(Equal("gpt"))
|
|
// Expect the disk UUID to be constant
|
|
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
|
// 5 partitions (boot, oem, recovery, state and persistent)
|
|
Expect(len(table.GetPartitions())).To(Equal(5))
|
|
// Cast the boot partition into specific type to check the type and such
|
|
part := table.GetPartitions()[0]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// Should be BIOS boot type
|
|
Expect(partition.Type).To(Equal(gpt.BIOSBoot))
|
|
// should have boot label
|
|
Expect(partition.Name).To(Equal(cnst.BiosPartName))
|
|
// Should have predictable UUID
|
|
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
|
|
for i := 1; i < len(table.GetPartitions()); i++ {
|
|
part := table.GetPartitions()[i]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// all of them should have the Linux fs type
|
|
Expect(partition.Type).To(Equal(gpt.LinuxFilesystem))
|
|
}
|
|
})
|
|
})
|
|
Describe("DeployImage", Label("DeployImage"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
var cmdFail string
|
|
BeforeEach(func() {
|
|
sourceDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
cmdFail = ""
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{
|
|
FS: cnst.LinuxImgFs,
|
|
Size: 16,
|
|
Source: v1.NewDirSrc(sourceDir),
|
|
MountPoint: destDir,
|
|
File: filepath.Join(destDir, "image.img"),
|
|
Label: "some_label",
|
|
}
|
|
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
|
if cmdFail == cmd {
|
|
return []byte{}, errors.New("Command failed")
|
|
}
|
|
switch cmd {
|
|
default:
|
|
GinkgoWriter.Println(fmt.Sprintf("Command %s called but we dont catch it", cmd))
|
|
return []byte{}, nil
|
|
}
|
|
}
|
|
})
|
|
It("Deploys an image from a directory and leaves it mounted", func() {
|
|
Expect(el.DeployImage(img, true)).To(BeNil())
|
|
})
|
|
It("Deploys an image from a directory and leaves it unmounted", func() {
|
|
Expect(el.DeployImage(img, false)).To(BeNil())
|
|
})
|
|
It("Deploys an squashfs image from a directory", func() {
|
|
img.FS = cnst.SquashFs
|
|
Expect(el.DeployImage(img, true)).To(BeNil())
|
|
Expect(runner.MatchMilestones([][]string{
|
|
{
|
|
"mksquashfs", "/tmp/elemental-tmp", "/tmp/elemental/image.img",
|
|
"-b", "1024k", "-comp", "gzip",
|
|
},
|
|
}))
|
|
})
|
|
It("Deploys a file image and mounts it", func() {
|
|
sourceImg := "/source.img"
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).To(BeNil())
|
|
img.Source = v1.NewFileSrc(sourceImg)
|
|
img.MountPoint = destDir
|
|
Expect(el.DeployImage(img, true)).To(BeNil())
|
|
})
|
|
It("Deploys a file image and fails to mount it", func() {
|
|
sourceImg := "/source.img"
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).To(BeNil())
|
|
img.Source = v1.NewFileSrc(sourceImg)
|
|
img.MountPoint = destDir
|
|
mounter.ErrorOnMount = true
|
|
_, err = el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Deploys a file image and fails to label it", func() {
|
|
sourceImg := "/source.img"
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).To(BeNil())
|
|
img.Source = v1.NewFileSrc(sourceImg)
|
|
img.MountPoint = destDir
|
|
cmdFail = "tune2fs"
|
|
_, err = el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Fails creating the squashfs filesystem", func() {
|
|
cmdFail = "mksquashfs"
|
|
img.FS = cnst.SquashFs
|
|
_, err := el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
Expect(runner.MatchMilestones([][]string{
|
|
{
|
|
"mksquashfs", "/tmp/elemental-tmp", "/tmp/elemental/image.img",
|
|
"-b", "1024k", "-comp", "gzip",
|
|
},
|
|
}))
|
|
})
|
|
It("Fails formatting the image", func() {
|
|
cmdFail = "mkfs.ext2"
|
|
_, err := el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Fails mounting the image", func() {
|
|
mounter.ErrorOnMount = true
|
|
_, err := el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Fails unmounting the image after copying", func() {
|
|
mounter.ErrorOnUnmount = true
|
|
_, err := el.DeployImage(img, false)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("DumpSource", Label("dump"), func() {
|
|
var e *elemental.Elemental
|
|
var destDir string
|
|
BeforeEach(func() {
|
|
var err error
|
|
e = elemental.NewElemental(config)
|
|
destDir, err = fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
})
|
|
It("Copies files from a directory source", func() {
|
|
rsyncCount := 0
|
|
src := ""
|
|
dest := ""
|
|
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
|
if cmd == cnst.Rsync {
|
|
rsyncCount += 1
|
|
src = args[len(args)-2]
|
|
dest = args[len(args)-1]
|
|
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
_, err := e.DumpSource("/dest", v1.NewDirSrc("/source"))
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(rsyncCount).To(Equal(1))
|
|
Expect(src).To(HaveSuffix("/source/"))
|
|
Expect(dest).To(HaveSuffix("/dest/"))
|
|
})
|
|
It("Unpacks a docker image to target", Label("docker"), func() {
|
|
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
|
|
Expect(err).To(BeNil())
|
|
})
|
|
It("Unpacks a docker image to target with cosign validation", Label("docker", "cosign"), func() {
|
|
config.Cosign = true
|
|
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
|
|
Expect(err).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{"cosign", "verify", "docker/image:latest"}}))
|
|
})
|
|
It("Fails cosign validation", Label("cosign"), func() {
|
|
runner.ReturnError = errors.New("cosign error")
|
|
config.Cosign = true
|
|
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
|
|
Expect(err).NotTo(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{"cosign", "verify", "docker/image:latest"}}))
|
|
})
|
|
It("Copies image file to target", func() {
|
|
sourceImg := "/source.img"
|
|
destFile := filepath.Join(destDir, "active.img")
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
_, err = e.DumpSource(destFile, v1.NewFileSrc(sourceImg))
|
|
Expect(err).To(BeNil())
|
|
Expect(runner.IncludesCmds([][]string{{cnst.Rsync}}))
|
|
})
|
|
It("Fails to copy, source file is not present", func() {
|
|
_, err := e.DumpSource("whatever", v1.NewFileSrc("/source.img"))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("CheckActiveDeployment", Label("check"), func() {
|
|
It("deployment found", func() {
|
|
ghwTest := ghwMock.GhwMock{}
|
|
disk := sdkTypes.Disk{Name: "device", Partitions: []*sdkTypes.Partition{
|
|
{
|
|
Name: "device1",
|
|
FilesystemLabel: cnst.ActiveLabel,
|
|
},
|
|
}}
|
|
ghwTest.AddDisk(disk)
|
|
ghwTest.CreateDevices()
|
|
|
|
runner.ReturnValue = []byte(
|
|
fmt.Sprintf(
|
|
`{"blockdevices": [{"label": "%s", "type": "loop", "path": "/some/device"}]}`,
|
|
cnst.ActiveLabel,
|
|
),
|
|
)
|
|
e := elemental.NewElemental(config)
|
|
Expect(e.CheckActiveDeployment([]string{cnst.ActiveLabel, cnst.PassiveLabel})).To(BeTrue())
|
|
|
|
ghwTest.Clean()
|
|
})
|
|
|
|
It("Should not error out", func() {
|
|
runner.ReturnValue = []byte("")
|
|
e := elemental.NewElemental(config)
|
|
Expect(e.CheckActiveDeployment([]string{cnst.ActiveLabel, cnst.PassiveLabel})).To(BeFalse())
|
|
})
|
|
})
|
|
Describe("SelinuxRelabel", Label("SelinuxRelabel", "selinux"), func() {
|
|
var policyFile string
|
|
var relabelCmd []string
|
|
BeforeEach(func() {
|
|
// to mock the existance of setfiles command on non selinux hosts
|
|
err := fsutils.MkdirAll(fs, "/usr/sbin", cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
sbin, err := fs.RawPath("/usr/sbin")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
path := os.Getenv("PATH")
|
|
os.Setenv("PATH", fmt.Sprintf("%s:%s", sbin, path))
|
|
_, err = fs.Create("/usr/sbin/setfiles")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = fs.Chmod("/usr/sbin/setfiles", 0777)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
// to mock SELinux policy files
|
|
policyFile = filepath.Join(cnst.SELinuxTargetedPolicyPath, "policy.31")
|
|
err = fsutils.MkdirAll(fs, filepath.Dir(cnst.SELinuxTargetedContextFile), cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(cnst.SELinuxTargetedContextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = fsutils.MkdirAll(fs, cnst.SELinuxTargetedPolicyPath, cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(policyFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
relabelCmd = []string{
|
|
"setfiles", "-c", policyFile, "-e", "/dev", "-e", "/proc", "-e", "/sys",
|
|
"-F", cnst.SELinuxTargetedContextFile, "/",
|
|
}
|
|
})
|
|
It("does nothing if the context file is not found", func() {
|
|
err := fs.Remove(cnst.SELinuxTargetedContextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("/", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{}}))
|
|
})
|
|
It("does nothing if the policy file is not found", func() {
|
|
err := fs.Remove(policyFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("/", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{}}))
|
|
})
|
|
It("relabels the current root", func() {
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
|
|
runner.ClearCmds()
|
|
Expect(c.SelinuxRelabel("/", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
It("fails to relabel the current root", func() {
|
|
runner.ReturnError = errors.New("setfiles failure")
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("", true)).NotTo(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
It("ignores relabel failures", func() {
|
|
runner.ReturnError = errors.New("setfiles failure")
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("", false)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
It("relabels the given root-tree path", func() {
|
|
contextFile := filepath.Join("/root", cnst.SELinuxTargetedContextFile)
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(contextFile), cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(contextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
policyFile = filepath.Join("/root", policyFile)
|
|
err = fsutils.MkdirAll(fs, filepath.Join("/root", cnst.SELinuxTargetedPolicyPath), cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(policyFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
relabelCmd = []string{
|
|
"setfiles", "-c", policyFile, "-F", "-r", "/root", contextFile, "/root",
|
|
}
|
|
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("/root", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
})
|
|
Describe("GetIso", Label("GetIso", "iso"), func() {
|
|
var e *elemental.Elemental
|
|
BeforeEach(func() {
|
|
e = elemental.NewElemental(config)
|
|
})
|
|
It("Gets the iso and returns the temporary where it is stored", func() {
|
|
tmpDir, err := fsutils.TempDir(fs, "", "elemental-test")
|
|
Expect(err).To(BeNil())
|
|
err = fs.WriteFile(fmt.Sprintf("%s/fake.iso", tmpDir), []byte("Hi"), cnst.FilePerm)
|
|
Expect(err).To(BeNil())
|
|
iso := fmt.Sprintf("%s/fake.iso", tmpDir)
|
|
isoDir, err := e.GetIso(iso)
|
|
Expect(err).To(BeNil())
|
|
// Confirm that the iso is stored in isoDir
|
|
fsutils.Exists(fs, filepath.Join(isoDir, "cOs.iso"))
|
|
})
|
|
It("Fails if it cant find the iso", func() {
|
|
iso := "http://whatever"
|
|
cl.Error = true
|
|
e := elemental.NewElemental(config)
|
|
_, err := e.GetIso(iso)
|
|
Expect(err).ToNot(BeNil())
|
|
})
|
|
It("Fails if it cannot mount the iso", func() {
|
|
mounter.ErrorOnMount = true
|
|
tmpDir, err := fsutils.TempDir(fs, "", "elemental-test")
|
|
Expect(err).To(BeNil())
|
|
err = fs.WriteFile(fmt.Sprintf("%s/fake.iso", tmpDir), []byte("Hi"), cnst.FilePerm)
|
|
Expect(err).To(BeNil())
|
|
iso := fmt.Sprintf("%s/fake.iso", tmpDir)
|
|
_, err = e.GetIso(iso)
|
|
Expect(err).ToNot(BeNil())
|
|
Expect(err.Error()).To(ContainSubstring("mount error"))
|
|
})
|
|
})
|
|
Describe("UpdateSourcesFormDownloadedISO", Label("iso"), func() {
|
|
var e *elemental.Elemental
|
|
var activeImg, recoveryImg *v1.Image
|
|
BeforeEach(func() {
|
|
activeImg, recoveryImg = nil, nil
|
|
e = elemental.NewElemental(config)
|
|
})
|
|
It("updates active image", func() {
|
|
activeImg = &v1.Image{}
|
|
err := e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(activeImg.Source.IsDir()).To(BeTrue())
|
|
Expect(activeImg.Source.Value()).To(Equal("/some/dir/rootfs"))
|
|
Expect(recoveryImg).To(BeNil())
|
|
})
|
|
It("updates active and recovery image", func() {
|
|
activeImg = &v1.Image{File: "activeFile"}
|
|
recoveryImg = &v1.Image{}
|
|
err := e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(recoveryImg.Source.IsFile()).To(BeTrue())
|
|
Expect(recoveryImg.Source.Value()).To(Equal("activeFile"))
|
|
Expect(recoveryImg.Label).To(Equal(cnst.SystemLabel))
|
|
Expect(activeImg.Source.IsDir()).To(BeTrue())
|
|
Expect(activeImg.Source.Value()).To(Equal("/some/dir/rootfs"))
|
|
})
|
|
It("updates recovery only image", func() {
|
|
recoveryImg = &v1.Image{}
|
|
isoMnt := "/some/dir/iso"
|
|
err := fsutils.MkdirAll(fs, isoMnt, cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
recoverySquash := filepath.Join(isoMnt, cnst.RecoverySquashFile)
|
|
_, err = fs.Create(recoverySquash)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(recoveryImg.Source.IsFile()).To(BeTrue())
|
|
Expect(recoveryImg.Source.Value()).To(Equal(recoverySquash))
|
|
Expect(activeImg).To(BeNil())
|
|
})
|
|
It("fails to update recovery from active file", func() {
|
|
recoveryImg = &v1.Image{}
|
|
err := e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
})
|
|
Describe("CloudConfig", Label("CloudConfig", "cloud-config"), func() {
|
|
var e *elemental.Elemental
|
|
BeforeEach(func() {
|
|
e = elemental.NewElemental(config)
|
|
})
|
|
It("Copies the cloud config file", func() {
|
|
testString := "In a galaxy far far away..."
|
|
cloudInit := []string{"/config.yaml"}
|
|
err := fs.WriteFile(cloudInit[0], []byte(testString), cnst.FilePerm)
|
|
Expect(err).To(BeNil())
|
|
Expect(err).To(BeNil())
|
|
|
|
err = e.CopyCloudConfig(cloudInit)
|
|
Expect(err).To(BeNil())
|
|
configFilePath := fmt.Sprintf("%s/90_custom.yaml", cnst.OEMDir)
|
|
copiedFile, err := fs.ReadFile(configFilePath)
|
|
Expect(err).To(BeNil())
|
|
Expect(copiedFile).To(ContainSubstring(testString))
|
|
stat, err := fs.Stat(configFilePath)
|
|
Expect(err).To(BeNil())
|
|
Expect(int(stat.Mode().Perm())).To(Equal(cnst.ConfigPerm))
|
|
|
|
})
|
|
It("Doesnt do anything if the config file is not set", func() {
|
|
err := e.CopyCloudConfig([]string{})
|
|
Expect(err).To(BeNil())
|
|
})
|
|
})
|
|
Describe("SetDefaultGrubEntry", Label("SetDefaultGrubEntry", "grub"), func() {
|
|
It("Sets the default grub entry without issues", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(config.Fs.Mkdir("/tmp", cnst.DirPerm)).To(BeNil())
|
|
Expect(el.SetDefaultGrubEntry("/tmp", "/imgMountpoint", "dio")).To(BeNil())
|
|
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
|
|
Expect(err).To(BeNil())
|
|
Expect(varsParsed["default_menu_entry"]).To(Equal("dio"))
|
|
})
|
|
It("does nothing on empty default entry and no /etc/kairos-release", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil())
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
|
_, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
|
|
// Because it didnt do anything due to the entry being empty, the file should not be there
|
|
Expect(err).ToNot(BeNil())
|
|
_, err = config.Fs.Stat(filepath.Join("/tmp", cnst.GrubOEMEnv))
|
|
Expect(err).ToNot(BeNil())
|
|
})
|
|
It("loads /etc/kairos-release on empty default entry", func() {
|
|
err := fsutils.MkdirAll(config.Fs, "/imgMountPoint/etc", cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = config.Fs.WriteFile("/imgMountPoint/etc/kairos-release", []byte("GRUB_ENTRY_NAME=test"), cnst.FilePerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil())
|
|
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
|
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config)
|
|
Expect(err).To(BeNil())
|
|
Expect(varsParsed["default_menu_entry"]).To(Equal("test"))
|
|
|
|
})
|
|
It("Fails setting grubenv", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("nonexisting", "nonexisting", "default_entry")).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("FindKernelInitrd", Label("find"), func() {
|
|
BeforeEach(func() {
|
|
err := fsutils.MkdirAll(fs, "/path/boot", cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
})
|
|
It("finds kernel and initrd files", func() {
|
|
_, err := fs.Create("/path/boot/initrd")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
_, err = fs.Create("/path/boot/vmlinuz")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
k, i, err := el.FindKernelInitrd("/path")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(k).To(Equal("/path/boot/vmlinuz"))
|
|
Expect(i).To(Equal("/path/boot/initrd"))
|
|
})
|
|
It("fails if no initrd is found", func() {
|
|
_, err := fs.Create("/path/boot/vmlinuz")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
_, _, err = el.FindKernelInitrd("/path")
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
It("fails if no kernel is found", func() {
|
|
_, err := fs.Create("/path/boot/initrd")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
_, _, err = el.FindKernelInitrd("/path")
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
})
|
|
Describe("DeactivateDevices", Label("blkdeactivate"), func() {
|
|
It("calls blkdeactivat", func() {
|
|
el := elemental.NewElemental(config)
|
|
err := el.DeactivateDevices()
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(runner.CmdsMatch([][]string{{
|
|
"blkdeactivate", "--lvmoptions", "retry,wholevg",
|
|
"--dmoptions", "force,retry", "--errors",
|
|
}})).To(BeNil())
|
|
})
|
|
})
|
|
})
|