kairos-agent/pkg/elemental/elemental_test.go
Itxaka ddfa30a4c6
Drop luet, image extractor, drop build code and multiarch images (#20)
Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2023-05-16 16:06:49 +02:00

962 lines
32 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 (
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/jaypipes/ghw/pkg/block"
"github.com/kairos-io/kairos/v2/pkg/constants"
cnst "github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/elemental"
conf "github.com/kairos-io/kairos/v2/pkg/elementalConfig"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
v1mock "github.com/kairos-io/kairos/v2/tests/mocks"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/vfst"
"k8s.io/mount-utils"
)
const printOutput = `BYT;
/dev/loop0:50593792s:loopback:512:512:gpt:Loopback device:;`
const partTmpl = `
%d:%ss:%ss:2048s:ext4::type=83;`
func TestElementalSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Elemental test suite")
}
var _ = Describe("Elemental", Label("elemental"), func() {
var config *v1.Config
var runner *v1mock.FakeRunner
var logger v1.Logger
var syscall v1.SyscallInterface
var client *v1mock.FakeHTTPClient
var mounter *v1mock.ErrorMounter
var fs *vfst.TestFS
var cleanup func()
var extractor *v1mock.FakeImageExtractor
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
syscall = &v1mock.FakeSyscall{}
mounter = v1mock.NewErrorMounter()
client = &v1mock.FakeHTTPClient{}
logger = v1.NewNullLogger()
fs, cleanup, _ = vfst.NewTestFS(nil)
extractor = v1mock.NewFakeImageExtractor(logger)
config = conf.NewConfig(
conf.WithFs(fs),
conf.WithRunner(runner),
conf.WithLogger(logger),
conf.WithMounter(mounter),
conf.WithSyscall(syscall),
conf.WithClient(client),
conf.WithImageExtractor(extractor),
)
})
AfterEach(func() { cleanup() })
Describe("MountRWPartition", Label("mount"), func() {
var el *elemental.Elemental
var parts v1.ElementalPartitions
BeforeEach(func() {
parts = conf.NewInstallElementalParitions()
err := utils.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() {
parts = conf.NewInstallElementalParitions()
err := utils.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() {
parts = conf.NewInstallElementalParitions()
err := utils.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"}
})
It("Mounts file system image", func() {
runner.ReturnValue = []byte("/dev/loop")
Expect(el.MountImage(img)).To(BeNil())
Expect(img.LoopDevice).To(Equal("/dev/loop"))
})
It("Fails to set a loop device", Label("loop"), func() {
runner.ReturnError = errors.New("failed to set a loop device")
Expect(el.MountImage(img)).NotTo(BeNil())
Expect(img.LoopDevice).To(Equal(""))
})
It("Fails to mount a loop device", Label("loop"), func() {
runner.ReturnValue = []byte("/dev/loop")
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() {
runner.ReturnValue = []byte("/dev/loop")
el = elemental.NewElemental(config)
img = &v1.Image{MountPoint: "/some/mountpoint"}
Expect(el.MountImage(img)).To(BeNil())
Expect(img.LoopDevice).To(Equal("/dev/loop"))
})
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() {
runner.ReturnError = errors.New("failed to unset a loop device")
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),
}
_ = utils.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 := &v1.Partition{
Path: "/dev/device1",
FS: "ext4",
FilesystemLabel: "MY_LABEL",
}
Expect(el.FormatPartition(part)).To(BeNil())
})
})
Describe("PartitionAndFormatDevice", Label("PartitionAndFormatDevice", "partition", "format"), func() {
var el *elemental.Elemental
var cInit *v1mock.FakeCloudInitRunner
var partNum int
var printOut string
var failPart bool
var install *v1.InstallSpec
BeforeEach(func() {
cInit = &v1mock.FakeCloudInitRunner{ExecStages: []string{}, Error: false}
config.CloudInitRunner = cInit
el = elemental.NewElemental(config)
install = conf.NewInstallSpec(*config)
install.Target = "/some/device"
err := utils.MkdirAll(fs, "/some", cnst.DirPerm)
Expect(err).ToNot(HaveOccurred())
_, err = fs.Create("/some/device")
Expect(err).ToNot(HaveOccurred())
})
Describe("Successful run", func() {
var runFunc func(cmd string, args ...string) ([]byte, error)
var efiPartCmds, partCmds, biosPartCmds [][]string
BeforeEach(func() {
partNum, printOut = 0, printOutput
err := utils.MkdirAll(fs, "/some", cnst.DirPerm)
Expect(err).To(BeNil())
efiPartCmds = [][]string{
{
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mklabel", "gpt",
}, {
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mkpart", "efi", "fat32", "2048", "133119", "set", "1", "esp", "on",
}, {"mkfs.vfat", "-n", "COS_GRUB", "/some/device1"},
}
biosPartCmds = [][]string{
{
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mklabel", "gpt",
}, {
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mkpart", "bios", "", "2048", "4095", "set", "1", "bios_grub", "on",
}, {"wipefs", "--all", "/some/device1"},
}
// These commands are only valid for EFI case
partCmds = [][]string{
{
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mkpart", "oem", "ext4", "133120", "264191",
}, {"mkfs.ext4", "-L", "COS_OEM", "/some/device2"}, {
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mkpart", "recovery", "ext4", "264192", "17041407",
}, {"mkfs.ext4", "-L", "COS_RECOVERY", "/some/device3"}, {
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mkpart", "state", "ext4", "17041408", "48498687",
}, {"mkfs.ext4", "-L", "COS_STATE", "/some/device4"}, {
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
"mkpart", "persistent", "ext4", "48498688", "100%",
}, {"mkfs.ext4", "-L", "COS_PERSISTENT", "/some/device5"},
}
runFunc = func(cmd string, args ...string) ([]byte, error) {
switch cmd {
case "parted":
idx := 0
for i, arg := range args {
if arg == "mkpart" {
idx = i
break
}
}
if idx > 0 {
partNum++
printOut += fmt.Sprintf(partTmpl, partNum, args[idx+3], args[idx+4])
_, _ = fs.Create(fmt.Sprintf("/some/device%d", partNum))
}
return []byte(printOut), nil
default:
return []byte{}, nil
}
}
runner.SideEffect = runFunc
})
It("Successfully creates partitions and formats them, EFI boot", func() {
install.PartTable = v1.GPT
install.Firmware = v1.EFI
install.Partitions.SetFirmwarePartitions(v1.EFI, v1.GPT)
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
Expect(runner.MatchMilestones(append(efiPartCmds, partCmds...))).To(BeNil())
})
It("Successfully creates partitions and formats them, BIOS boot", func() {
install.PartTable = v1.GPT
install.Firmware = v1.BIOS
install.Partitions.SetFirmwarePartitions(v1.BIOS, v1.GPT)
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
Expect(runner.MatchMilestones(biosPartCmds)).To(BeNil())
})
})
Describe("Run with failures", func() {
var runFunc func(cmd string, args ...string) ([]byte, error)
BeforeEach(func() {
err := utils.MkdirAll(fs, "/some", cnst.DirPerm)
Expect(err).To(BeNil())
partNum, printOut = 0, printOutput
runFunc = func(cmd string, args ...string) ([]byte, error) {
switch cmd {
case "parted":
idx := 0
for i, arg := range args {
if arg == "mkpart" {
idx = i
break
}
}
if idx > 0 {
partNum++
printOut += fmt.Sprintf(partTmpl, partNum, args[idx+3], args[idx+4])
if failPart {
return []byte{}, errors.New("Failure")
}
_, _ = fs.Create(fmt.Sprintf("/some/device%d", partNum))
}
return []byte(printOut), nil
case "mkfs.ext4", "wipefs", "mkfs.vfat":
return []byte{}, errors.New("Failure")
default:
return []byte{}, nil
}
}
runner.SideEffect = runFunc
})
It("Fails creating efi partition", func() {
failPart = true
Expect(el.PartitionAndFormatDevice(install)).NotTo(BeNil())
// Failed to create first partition
Expect(partNum).To(Equal(1))
})
It("Fails formatting efi partition", func() {
failPart = false
Expect(el.PartitionAndFormatDevice(install)).NotTo(BeNil())
// Failed to format first partition
Expect(partNum).To(Equal(1))
})
})
})
Describe("DeployImage", Label("DeployImage"), func() {
var el *elemental.Elemental
var img *v1.Image
var cmdFail string
BeforeEach(func() {
sourceDir, err := utils.TempDir(fs, "", "elemental")
Expect(err).ShouldNot(HaveOccurred())
destDir, err := utils.TempDir(fs, "", "elemental")
Expect(err).ShouldNot(HaveOccurred())
cmdFail = ""
el = elemental.NewElemental(config)
img = &v1.Image{
FS: constants.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 {
case "losetup":
return []byte("/dev/loop"), nil
default:
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 = constants.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 := utils.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 := utils.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 := utils.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 = constants.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 copying the image if source does not exist", func() {
img.Source = v1.NewDirSrc("/welp")
_, 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 = utils.TempDir(fs, "", "elemental")
Expect(err).ShouldNot(HaveOccurred())
})
It("Copies files from a directory source", func() {
sourceDir, err := utils.TempDir(fs, "", "elemental")
Expect(err).ShouldNot(HaveOccurred())
_, err = e.DumpSource(destDir, v1.NewDirSrc(sourceDir))
Expect(err).To(BeNil())
})
It("Fails if source directory does not exist", func() {
_, err := e.DumpSource(destDir, v1.NewDirSrc("/welp"))
Expect(err).ToNot(BeNil())
})
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"
_, err := fs.Create(sourceImg)
Expect(err).To(BeNil())
destFile := filepath.Join(destDir, "active.img")
_, err = fs.Stat(destFile)
Expect(err).NotTo(BeNil())
_, err = e.DumpSource(destFile, v1.NewFileSrc(sourceImg))
Expect(err).To(BeNil())
_, err = fs.Stat(destFile)
Expect(err).To(BeNil())
})
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 := v1mock.GhwMock{}
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
{
Name: "device1",
FilesystemLabel: cnst.ActiveLabel,
},
}}
ghwTest.AddDisk(disk)
ghwTest.CreateDevices()
defer ghwTest.Clean()
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())
})
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 := utils.MkdirAll(fs, "/usr/sbin", constants.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(constants.SELinuxTargetedPolicyPath, "policy.31")
err = utils.MkdirAll(fs, filepath.Dir(constants.SELinuxTargetedContextFile), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create(constants.SELinuxTargetedContextFile)
Expect(err).ShouldNot(HaveOccurred())
err = utils.MkdirAll(fs, constants.SELinuxTargetedPolicyPath, constants.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", constants.SELinuxTargetedContextFile, "/",
}
})
It("does nothing if the context file is not found", func() {
err := fs.Remove(constants.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", constants.SELinuxTargetedContextFile)
err := utils.MkdirAll(fs, filepath.Dir(contextFile), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create(contextFile)
Expect(err).ShouldNot(HaveOccurred())
policyFile = filepath.Join("/root", policyFile)
err = utils.MkdirAll(fs, filepath.Join("/root", constants.SELinuxTargetedPolicyPath), constants.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 := utils.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
utils.Exists(fs, filepath.Join(isoDir, "cOs.iso"))
})
It("Fails if it cant find the iso", func() {
iso := "whatever"
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 := utils.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 := utils.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())
copiedFile, err := fs.ReadFile(fmt.Sprintf("%s/90_custom.yaml", cnst.OEMDir))
Expect(err).To(BeNil())
Expect(copiedFile).To(ContainSubstring(testString))
})
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(el.SetDefaultGrubEntry("/mountpoint", "/imgMountpoint", "default_entry")).To(BeNil())
})
It("does nothing on empty default entry and no /etc/os-release", func() {
el := elemental.NewElemental(config)
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
// No grub2-editenv command called
Expect(runner.CmdsMatch([][]string{{"grub2-editenv"}})).NotTo(BeNil())
})
It("loads /etc/os-release on empty default entry", func() {
err := utils.MkdirAll(config.Fs, "/imgMountPoint/etc", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = config.Fs.WriteFile("/imgMountPoint/etc/os-release", []byte("GRUB_ENTRY_NAME=test"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
el := elemental.NewElemental(config)
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
// Calls grub2-editenv with the loaded content from /etc/os-release
editEnv := utils.FindCommand("grub2-editenv", []string{"grub2-editenv", "grub-editenv"})
Expect(runner.CmdsMatch([][]string{
{editEnv, "/mountpoint/grub_oem_env", "set", "default_menu_entry=test"},
})).To(BeNil())
})
It("Fails setting grubenv", func() {
runner.ReturnError = errors.New("failure")
el := elemental.NewElemental(config)
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "default_entry")).NotTo(BeNil())
})
})
Describe("FindKernelInitrd", Label("find"), func() {
BeforeEach(func() {
err := utils.MkdirAll(fs, "/path/boot", constants.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())
})
})
})
// PathInMountPoints will check if the given path is in the mountPoints list
func pathInMountPoints(mounter mount.Interface, path string) bool {
mountPoints, _ := mounter.List()
for _, m := range mountPoints {
if path == m.Path {
return true
}
}
return false
}