elemental-toolkit/pkg/utils/utils_test.go

1315 lines
49 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 utils_test
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/jaypipes/ghw/pkg/block"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
conf "github.com/rancher/elemental-toolkit/v2/pkg/config"
"github.com/rancher/elemental-toolkit/v2/pkg/constants"
"github.com/rancher/elemental-toolkit/v2/pkg/mocks"
"github.com/rancher/elemental-toolkit/v2/pkg/types"
"github.com/rancher/elemental-toolkit/v2/pkg/utils"
)
func getNamesFromListFiles(list []fs.DirEntry) []string {
var names []string
for _, f := range list {
names = append(names, f.Name())
}
return names
}
var _ = Describe("Utils", Label("utils"), func() {
var config *types.Config
var runner *mocks.FakeRunner
var realRunner *types.RealRunner
var logger types.Logger
var syscall *mocks.FakeSyscall
var client *mocks.FakeHTTPClient
var mounter *mocks.FakeMounter
var extractor *mocks.FakeImageExtractor
var fs vfs.FS
var cleanup func()
BeforeEach(func() {
runner = mocks.NewFakeRunner()
syscall = &mocks.FakeSyscall{}
mounter = mocks.NewFakeMounter()
client = &mocks.FakeHTTPClient{}
logger = types.NewNullLogger()
realRunner = &types.RealRunner{Logger: logger}
extractor = mocks.NewFakeImageExtractor(logger)
// Ensure /tmp exists in the VFS
fs, cleanup, _ = vfst.NewTestFS(nil)
fs.Mkdir("/tmp", constants.DirPerm)
fs.Mkdir("/run", constants.DirPerm)
fs.Mkdir("/etc", constants.DirPerm)
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("Chroot", Label("chroot"), func() {
var chroot *utils.Chroot
BeforeEach(func() {
chroot = utils.NewChroot(
"/whatever",
config,
)
})
Describe("ChrootedCallback method", func() {
It("runs a callback in a chroot", func() {
err := utils.ChrootedCallback(config, "/somepath", nil, func() error {
return nil
})
Expect(err).ShouldNot(HaveOccurred())
err = utils.ChrootedCallback(config, "/somepath", nil, func() error {
return fmt.Errorf("callback error")
})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("callback error"))
})
})
Describe("on success", func() {
It("command should be called in the chroot", func() {
_, err := chroot.Run("chroot-command")
Expect(err).To(BeNil())
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
})
It("commands should be called with a customized chroot", func() {
chroot.SetExtraMounts(map[string]string{"/real/path": "/in/chroot/path"})
Expect(chroot.Prepare()).To(BeNil())
defer chroot.Close()
_, err := chroot.Run("chroot-command")
Expect(err).To(BeNil())
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
_, err = chroot.Run("chroot-another-command")
Expect(err).To(BeNil())
})
It("runs a callback in a custom chroot", func() {
called := false
callback := func() error {
called = true
return nil
}
err := chroot.RunCallback(callback)
Expect(err).To(BeNil())
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
Expect(called).To(BeTrue())
})
})
Describe("on failure", func() {
It("should return error if chroot-command fails", func() {
runner.ReturnError = errors.New("run error")
_, err := chroot.Run("chroot-command")
Expect(err).NotTo(BeNil())
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
})
It("should return error if callback fails", func() {
called := false
callback := func() error {
called = true
return errors.New("Callback error")
}
err := chroot.RunCallback(callback)
Expect(err).NotTo(BeNil())
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
Expect(called).To(BeTrue())
})
It("should return error if preparing twice before closing", func() {
Expect(chroot.Prepare()).To(BeNil())
defer chroot.Close()
Expect(chroot.Prepare()).NotTo(BeNil())
Expect(chroot.Close()).To(BeNil())
Expect(chroot.Prepare()).To(BeNil())
})
It("should return error if failed to chroot", func() {
syscall.ErrorOnChroot = true
_, err := chroot.Run("chroot-command")
Expect(err).ToNot(BeNil())
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
Expect(err.Error()).To(ContainSubstring("chroot error"))
})
It("should return error if failed to mount on prepare", Label("mount"), func() {
mounter.ErrorOnMount = true
_, err := chroot.Run("chroot-command")
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(ContainSubstring("mount error"))
})
It("should return error if failed to unmount on close", Label("unmount"), func() {
mounter.ErrorOnUnmount = true
_, err := chroot.Run("chroot-command")
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(ContainSubstring("failed closing chroot"))
})
})
})
Describe("TestBootedFrom", Label("BootedFrom"), func() {
It("returns true if we are booting from label FAKELABEL", func() {
runner.ReturnValue = []byte("")
Expect(utils.BootedFrom(runner, "FAKELABEL")).To(BeFalse())
})
It("returns false if we are not booting from label FAKELABEL", func() {
runner.ReturnValue = []byte("FAKELABEL")
Expect(utils.BootedFrom(runner, "FAKELABEL")).To(BeTrue())
})
})
Describe("GetDeviceByLabel", Label("lsblk", "partitions"), func() {
var cmds [][]string
BeforeEach(func() {
cmds = [][]string{
{"udevadm", "settle"},
}
})
It("returns found device", func() {
ghwTest := mocks.GhwMock{}
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
{
Name: "device1",
FilesystemLabel: "FAKE",
},
}}
ghwTest.AddDisk(disk)
ghwTest.CreateDevices()
defer ghwTest.Clean()
out, err := utils.GetDeviceByLabel(runner, "FAKE", 1)
Expect(err).To(BeNil())
Expect(out).To(Equal("/dev/device1"))
Expect(runner.CmdsMatch(cmds)).To(BeNil())
})
It("fails if no device is found in two attempts", func() {
_, err := utils.GetDeviceByLabel(runner, "FAKE", 2)
Expect(err).NotTo(BeNil())
Expect(runner.CmdsMatch(append(cmds, cmds...))).To(BeNil())
})
})
Describe("GetAllPartitions", Label("lsblk", "partitions"), func() {
var ghwTest mocks.GhwMock
BeforeEach(func() {
ghwTest = mocks.GhwMock{}
disk1 := block.Disk{
Name: "sda",
Partitions: []*block.Partition{
{
Name: "sda1Test",
},
{
Name: "sda2Test",
},
},
}
disk2 := block.Disk{
Name: "sdb",
Partitions: []*block.Partition{
{
Name: "sdb1Test",
},
},
}
ghwTest.AddDisk(disk1)
ghwTest.AddDisk(disk2)
ghwTest.CreateDevices()
})
AfterEach(func() {
ghwTest.Clean()
})
It("returns all found partitions", func() {
parts, err := utils.GetAllPartitions()
Expect(err).To(BeNil())
var devices []string
for _, p := range parts {
devices = append(devices, p.Path)
}
Expect(devices).To(ContainElement(ContainSubstring("sda1Test")))
Expect(devices).To(ContainElement(ContainSubstring("sda2Test")))
Expect(devices).To(ContainElement(ContainSubstring("sdb1Test")))
})
})
Describe("GetPartitionFS", Label("lsblk", "partitions"), func() {
var ghwTest mocks.GhwMock
BeforeEach(func() {
ghwTest = mocks.GhwMock{}
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
{
Name: "device1",
Type: "xfs",
},
{
Name: "device2",
},
}}
ghwTest.AddDisk(disk)
ghwTest.CreateDevices()
})
AfterEach(func() {
ghwTest.Clean()
})
It("returns found device with plain partition device", func() {
pFS, err := utils.GetPartitionFS("device1")
Expect(err).To(BeNil())
Expect(pFS).To(Equal("xfs"))
})
It("returns found device with full partition device", func() {
pFS, err := utils.GetPartitionFS("/dev/device1")
Expect(err).To(BeNil())
Expect(pFS).To(Equal("xfs"))
})
It("fails if no partition is found", func() {
_, err := utils.GetPartitionFS("device2")
Expect(err).NotTo(BeNil())
})
})
Describe("CosignVerify", Label("cosign"), func() {
It("runs a keyless verification", func() {
_, err := utils.CosignVerify(fs, runner, "some/image:latest", "", true)
Expect(err).To(BeNil())
Expect(runner.CmdsMatch([][]string{{"cosign", "-d=true", "some/image:latest"}})).To(BeNil())
})
It("runs a verification using a public key", func() {
_, err := utils.CosignVerify(fs, runner, "some/image:latest", "https://mykey.pub", false)
Expect(err).To(BeNil())
Expect(runner.CmdsMatch(
[][]string{{"cosign", "-key", "https://mykey.pub", "some/image:latest"}},
)).To(BeNil())
})
It("Fails to to create temporary directories", func() {
_, err := utils.CosignVerify(vfs.NewReadOnlyFS(fs), runner, "some/image:latest", "", true)
Expect(err).NotTo(BeNil())
})
})
Describe("Reboot and shutdown", Label("reboot", "shutdown"), func() {
It("reboots", func() {
start := time.Now()
utils.Reboot(runner, 2)
duration := time.Since(start)
Expect(runner.CmdsMatch([][]string{{"reboot", "-f"}})).To(BeNil())
Expect(duration.Seconds() >= 2).To(BeTrue())
})
It("shuts down", func() {
start := time.Now()
utils.Shutdown(runner, 3)
duration := time.Since(start)
Expect(runner.CmdsMatch([][]string{{"poweroff", "-f"}})).To(BeNil())
Expect(duration.Seconds() >= 3).To(BeTrue())
})
})
Describe("GetFullDeviceByLabel", Label("lsblk", "partitions"), func() {
var cmds [][]string
BeforeEach(func() {
cmds = [][]string{
{"udevadm", "settle"},
}
})
It("returns found types.Partition", func() {
var flags []string
ghwTest := mocks.GhwMock{}
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
{
Name: "device1",
FilesystemLabel: "FAKE",
Type: "fakefs",
MountPoint: "/mnt/fake",
SizeBytes: 0,
},
}}
ghwTest.AddDisk(disk)
ghwTest.CreateDevices()
defer ghwTest.Clean()
out, err := utils.GetFullDeviceByLabel(runner, "FAKE", 1)
Expect(err).To(BeNil())
Expect(out.FilesystemLabel).To(Equal("FAKE"))
Expect(out.Size).To(Equal(uint(0)))
Expect(out.FS).To(Equal("fakefs"))
Expect(out.MountPoint).To(Equal("/mnt/fake"))
Expect(out.Flags).To(Equal(flags))
Expect(runner.CmdsMatch(cmds)).To(BeNil())
})
It("fails to run lsblk", func() {
runner.ReturnError = errors.New("failed running lsblk")
_, err := utils.GetFullDeviceByLabel(runner, "FAKE", 1)
Expect(err).To(HaveOccurred())
Expect(runner.CmdsMatch(cmds)).To(BeNil())
})
It("fails to parse json output", func() {
runner.ReturnValue = []byte(`{"invalidobject": []}`)
_, err := utils.GetFullDeviceByLabel(runner, "FAKE", 1)
Expect(err).To(HaveOccurred())
Expect(runner.CmdsMatch(cmds)).To(BeNil())
})
It("fails if no device is found in two attempts", func() {
runner.ReturnValue = []byte(`{"blockdevices":[{"label":"something","type": "part"}]}`)
_, err := utils.GetFullDeviceByLabel(runner, "FAKE", 2)
Expect(err).To(HaveOccurred())
Expect(runner.CmdsMatch(append(cmds, cmds...))).To(BeNil())
})
})
Describe("CopyFile", Label("CopyFile"), func() {
It("Copies source file to target file", func() {
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/some/file")
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Stat("/some/otherfile")
Expect(err).Should(HaveOccurred())
Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).ShouldNot(HaveOccurred())
e, err := utils.Exists(fs, "/some/otherfile")
Expect(err).ShouldNot(HaveOccurred())
Expect(e).To(BeTrue())
})
It("Copies source file to target folder", func() {
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = utils.MkdirAll(fs, "/someotherfolder", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/some/file")
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Stat("/someotherfolder/file")
Expect(err).Should(HaveOccurred())
Expect(utils.CopyFile(fs, "/some/file", "/someotherfolder")).ShouldNot(HaveOccurred())
e, err := utils.Exists(fs, "/someotherfolder/file")
Expect(err).ShouldNot(HaveOccurred())
Expect(e).To(BeTrue())
})
It("Fails to open non existing file", func() {
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil())
_, err = fs.Stat("/some/otherfile")
Expect(err).NotTo(BeNil())
})
It("Fails to copy on non writable target", func() {
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
fs.Create("/some/file")
_, err = fs.Stat("/some/otherfile")
Expect(err).NotTo(BeNil())
fs = vfs.NewReadOnlyFS(fs)
Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil())
_, err = fs.Stat("/some/otherfile")
Expect(err).NotTo(BeNil())
})
})
Describe("CreateDirStructure", Label("CreateDirStructure"), func() {
It("Creates essential directories", func() {
dirList := []string{"sys", "proc", "dev", "tmp", "boot", "oem"}
for _, dir := range dirList {
_, err := fs.Stat(fmt.Sprintf("/my/root/%s", dir))
Expect(err).NotTo(BeNil())
}
Expect(utils.CreateDirStructure(fs, "/my/root")).To(BeNil())
for _, dir := range dirList {
fi, err := fs.Stat(fmt.Sprintf("/my/root/%s", dir))
Expect(err).To(BeNil())
if fi.Name() == "tmp" {
Expect(fmt.Sprintf("%04o", fi.Mode().Perm())).To(Equal("0777"))
Expect(fi.Mode() & os.ModeSticky).NotTo(Equal(0))
}
if fi.Name() == "sys" {
Expect(fmt.Sprintf("%04o", fi.Mode().Perm())).To(Equal("0555"))
}
}
})
It("Fails on non writable target", func() {
fs = vfs.NewReadOnlyFS(fs)
Expect(utils.CreateDirStructure(fs, "/my/root")).NotTo(BeNil())
})
})
Describe("Rsync tests", Label("rsync"), func() {
var sourceDir, destDir string
var err error
BeforeEach(func() {
sourceDir, err = utils.TempDir(fs, "", "elementalsource")
Expect(err).ShouldNot(HaveOccurred())
destDir, err = utils.TempDir(fs, "", "elementaltarget")
Expect(err).ShouldNot(HaveOccurred())
})
It("Copies all files from source to target", func() {
for i := 0; i < 5; i++ {
_, _ = utils.TempFile(fs, sourceDir, "file*")
}
Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir)).To(BeNil())
filesDest, err := fs.ReadDir(destDir)
Expect(err).To(BeNil())
destNames := getNamesFromListFiles(filesDest)
filesSource, err := fs.ReadDir(sourceDir)
Expect(err).To(BeNil())
SourceNames := getNamesFromListFiles(filesSource)
// Should be the same files in both dirs now
Expect(destNames).To(Equal(SourceNames))
})
It("Copies all files from source to target respecting excludes", func() {
utils.MkdirAll(fs, filepath.Join(sourceDir, "host"), constants.DirPerm)
utils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm)
// /tmp/run would be excluded as well, as we define an exclude without the "/" prefix
utils.MkdirAll(fs, filepath.Join(sourceDir, "tmp", "run"), constants.DirPerm)
for i := 0; i < 5; i++ {
_, _ = utils.TempFile(fs, sourceDir, "file*")
}
Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir, "host", "run")).To(BeNil())
filesDest, err := fs.ReadDir(destDir)
Expect(err).To(BeNil())
destNames := getNamesFromListFiles(filesDest)
filesSource, err := fs.ReadDir(sourceDir)
Expect(err).To(BeNil())
SourceNames := getNamesFromListFiles(filesSource)
// Shouldn't be the same
Expect(destNames).ToNot(Equal(SourceNames))
expected := []string{}
for _, s := range SourceNames {
if s != "host" && s != "run" {
expected = append(expected, s)
}
}
Expect(destNames).To(Equal(expected))
// /tmp/run is not copied over
Expect(utils.Exists(fs, filepath.Join(destDir, "tmp", "run"))).To(BeFalse())
})
It("Copies all files from source to target respecting excludes with '/' prefix", func() {
utils.MkdirAll(fs, filepath.Join(sourceDir, "host"), constants.DirPerm)
utils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm)
utils.MkdirAll(fs, filepath.Join(sourceDir, "var", "run"), constants.DirPerm)
utils.MkdirAll(fs, filepath.Join(sourceDir, "tmp", "host"), constants.DirPerm)
Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir, "/host", "/run")).To(BeNil())
filesDest, err := fs.ReadDir(destDir)
Expect(err).To(BeNil())
destNames := getNamesFromListFiles(filesDest)
filesSource, err := fs.ReadDir(sourceDir)
Expect(err).To(BeNil())
sourceNames := getNamesFromListFiles(filesSource)
// Shouldn't be the same
Expect(destNames).ToNot(Equal(sourceNames))
Expect(utils.Exists(fs, filepath.Join(destDir, "var", "run"))).To(BeTrue())
Expect(utils.Exists(fs, filepath.Join(destDir, "tmp", "host"))).To(BeTrue())
Expect(utils.Exists(fs, filepath.Join(destDir, "host"))).To(BeFalse())
Expect(utils.Exists(fs, filepath.Join(destDir, "run"))).To(BeFalse())
})
It("Copies all files from source to target respecting excludes with wildcards", func() {
utils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm)
utils.MkdirAll(fs, filepath.Join(sourceDir, "var", "run"), constants.DirPerm)
Expect(fs.WriteFile(filepath.Join(sourceDir, "run", "testfile"), []byte{}, constants.DirPerm)).To(Succeed())
Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir, "/run/*")).To(BeNil())
Expect(utils.Exists(fs, filepath.Join(destDir, "var", "run"))).To(BeTrue())
Expect(utils.Exists(fs, filepath.Join(destDir, "run"))).To(BeTrue())
Expect(utils.Exists(fs, filepath.Join(destDir, "run", "testfile"))).To(BeFalse())
})
It("Mirrors all files from source to destination deleting pre-existing files in destination if needed", func() {
utils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm)
utils.MkdirAll(fs, filepath.Join(sourceDir, "var", "run"), constants.DirPerm)
Expect(fs.WriteFile(filepath.Join(sourceDir, "run", "testfile"), []byte{}, constants.DirPerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(destDir, "testfile"), []byte{}, constants.DirPerm)).To(Succeed())
Expect(utils.MirrorData(logger, realRunner, fs, sourceDir, destDir)).To(BeNil())
filesDest, err := fs.ReadDir(destDir)
Expect(err).To(BeNil())
destNames := getNamesFromListFiles(filesDest)
filesSource, err := fs.ReadDir(sourceDir)
Expect(err).To(BeNil())
sourceNames := getNamesFromListFiles(filesSource)
// Should be the same
Expect(destNames).To(Equal(sourceNames))
// pre-exising file in destination deleted if this is not part of source
Expect(utils.Exists(fs, filepath.Join(destDir, "testfile"))).To(BeFalse())
})
It("should not fail if dirs are empty", func() {
Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir)).To(BeNil())
})
It("should fail if destination does not exist", func() {
fs.RemoveAll(destDir)
Expect(utils.SyncData(logger, realRunner, nil, sourceDir, "/welp")).NotTo(BeNil())
})
It("should fail if source does not exist", func() {
fs.RemoveAll(sourceDir)
Expect(utils.SyncData(logger, realRunner, nil, "/welp", destDir)).NotTo(BeNil())
})
})
Describe("IsLocalURI", Label("uri"), func() {
It("Detects a local url", func() {
local, err := utils.IsLocalURI("file://some/path")
Expect(err).To(BeNil())
Expect(local).To(BeTrue())
})
It("Detects a local path", func() {
local, err := utils.IsLocalURI("/some/path")
Expect(err).To(BeNil())
Expect(local).To(BeTrue())
})
It("Detects a remote uri", func() {
local, err := utils.IsLocalURI("http://something.org")
Expect(err).To(BeNil())
Expect(local).To(BeFalse())
})
It("Detects a remote uri", func() {
local, err := utils.IsLocalURI("some.domain.org:33/some/path")
Expect(err).To(BeNil())
Expect(local).To(BeFalse())
local, err = utils.IsLocalURI("some.domain.org/some/path:latest")
Expect(err).To(BeNil())
Expect(local).To(BeFalse())
})
It("Fails on invalid URL", func() {
local, err := utils.IsLocalURI("$htt:|//insane.stuff")
Expect(err).NotTo(BeNil())
Expect(local).To(BeFalse())
})
})
Describe("IsHTTPURI", Label("uri"), func() {
It("Detects a http url", func() {
local, err := utils.IsHTTPURI("http://domain.org/path")
Expect(err).To(BeNil())
Expect(local).To(BeTrue())
})
It("Detects a https url", func() {
local, err := utils.IsHTTPURI("https://domain.org/path")
Expect(err).To(BeNil())
Expect(local).To(BeTrue())
})
It("Detects it is a non http URL", func() {
local, err := utils.IsHTTPURI("file://path")
Expect(err).To(BeNil())
Expect(local).To(BeFalse())
local, err = utils.IsHTTPURI("container.reg.org:1024/some/repository")
Expect(err).To(BeNil())
Expect(local).To(BeFalse())
})
It("Fails on invalid URL", func() {
local, err := utils.IsLocalURI("$htt:|//insane.stuff")
Expect(err).NotTo(BeNil())
Expect(local).To(BeFalse())
})
})
Describe("GetSource", Label("GetSource"), func() {
It("Fails on invalid url", func() {
Expect(utils.GetSource(*config, "$htt:|//insane.stuff", "/tmp/dest")).NotTo(BeNil())
})
It("Fails on readonly destination", func() {
config.Fs = vfs.NewReadOnlyFS(fs)
Expect(utils.GetSource(*config, "http://something.org", "/tmp/dest")).NotTo(BeNil())
})
It("Fails on non existing local source", func() {
Expect(utils.GetSource(*config, "/some/missing/file", "/tmp/dest")).NotTo(BeNil())
})
It("Fails on http client error", func() {
client.Error = true
url := "https://missing.io"
Expect(utils.GetSource(*config, url, "/tmp/dest")).NotTo(BeNil())
client.WasGetCalledWith(url)
})
It("Copies local file to destination", func() {
fs.Create("/tmp/file")
Expect(utils.GetSource(*config, "file:///tmp/file", "/tmp/dest")).To(BeNil())
_, err := fs.Stat("/tmp/dest")
Expect(err).To(BeNil())
})
})
Describe("ValidContainerReference", Label("reference"), func() {
It("Returns true on valid references", func() {
Expect(utils.ValidContainerReference("opensuse/leap:15.3")).To(BeTrue())
Expect(utils.ValidContainerReference("opensuse")).To(BeTrue())
Expect(utils.ValidContainerReference("registry.suse.com/opensuse/something")).To(BeTrue())
Expect(utils.ValidContainerReference("registry.suse.com:8080/something:253")).To(BeTrue())
})
It("Returns false on invalid references", func() {
Expect(utils.ValidContainerReference("opensuse/leap:15+3")).To(BeFalse())
Expect(utils.ValidContainerReference("opensusE")).To(BeFalse())
Expect(utils.ValidContainerReference("registry.suse.com:8080/Something:253")).To(BeFalse())
Expect(utils.ValidContainerReference("http://registry.suse.com:8080/something:253")).To(BeFalse())
})
})
Describe("ValidTaggedContainerReference", Label("reference"), func() {
It("Returns true on valid references including explicit tag", func() {
Expect(utils.ValidTaggedContainerReference("opensuse/leap:15.3")).To(BeTrue())
Expect(utils.ValidTaggedContainerReference("registry.suse.com/opensuse/something:latest")).To(BeTrue())
Expect(utils.ValidTaggedContainerReference("registry.suse.com:8080/something:253")).To(BeTrue())
})
It("Returns false on valid references without explicit tag", func() {
Expect(utils.ValidTaggedContainerReference("opensuse")).To(BeFalse())
Expect(utils.ValidTaggedContainerReference("registry.suse.com/opensuse/something")).To(BeFalse())
Expect(utils.ValidTaggedContainerReference("registry.suse.com:8080/something")).To(BeFalse())
})
})
Describe("DirSize", Label("fs"), func() {
BeforeEach(func() {
err := utils.MkdirAll(fs, "/folder/subfolder", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
f, err := fs.Create("/folder/file")
Expect(err).ShouldNot(HaveOccurred())
err = f.Truncate(1024)
Expect(err).ShouldNot(HaveOccurred())
f, err = fs.Create("/folder/subfolder/file")
Expect(err).ShouldNot(HaveOccurred())
err = f.Truncate(2048)
Expect(err).ShouldNot(HaveOccurred())
})
It("Returns the expected size of a test folder", func() {
size, err := utils.DirSize(fs, "/folder")
Expect(err).ShouldNot(HaveOccurred())
Expect(size).To(Equal(int64(3072)))
})
It("Returns the size of a test folder when skipping subdirectories", func() {
size, err := utils.DirSize(fs, "/folder", "/folder/subfolder")
Expect(err).ShouldNot(HaveOccurred())
Expect(size).To(Equal(int64(1024)))
})
It("Fails with permission denied", func() {
err := fs.Chmod("/folder/subfolder", 0600)
Expect(err).ShouldNot(HaveOccurred())
_, err = utils.DirSize(fs, "/folder")
Expect(err).Should(HaveOccurred())
})
})
Describe("ResolveLink", func() {
var rootDir, file, relSymlink, absSymlink, nestSymlink, brokenSymlink string
BeforeEach(func() {
// The root directory
rootDir = "/some/root"
Expect(utils.MkdirAll(fs, rootDir, constants.DirPerm)).To(Succeed())
// The target file of all symlinks
file = "/path/with/needle/findme.extension"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(file)), constants.DirPerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(rootDir, file), []byte("some data"), constants.FilePerm)).To(Succeed())
// A symlink pointing to a relative path
relSymlink = "/path/to/symlink/pointing-to-file"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(relSymlink)), constants.DirPerm)).To(Succeed())
Expect(fs.Symlink("../../with/needle/findme.extension", filepath.Join(rootDir, relSymlink))).To(Succeed())
// A symlink pointing to an absolute path
absSymlink = "/path/to/symlink/absolute-pointer"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(absSymlink)), constants.DirPerm)).To(Succeed())
Expect(fs.Symlink(file, filepath.Join(rootDir, absSymlink))).To(Succeed())
// A bunch of nested symlinks
nestSymlink = "/path/to/symlink/nested-pointer"
nestFst := "/path/to/symlink/nestFst"
nest2nd := "/path/to/nest2nd"
nest3rd := "/path/with/nest3rd"
Expect(fs.Symlink("nestFst", filepath.Join(rootDir, nestSymlink))).To(Succeed())
Expect(fs.Symlink(nest2nd, filepath.Join(rootDir, nestFst))).To(Succeed())
Expect(fs.Symlink("../with/nest3rd", filepath.Join(rootDir, nest2nd))).To(Succeed())
Expect(fs.Symlink("./needle/findme.extension", filepath.Join(rootDir, nest3rd))).To(Succeed())
// A broken symlink
brokenSymlink = "/path/to/symlink/broken"
Expect(fs.Symlink("/path/to/nowhere", filepath.Join(rootDir, brokenSymlink))).To(Succeed())
})
It("resolves a simple relative symlink", func() {
systemPath := filepath.Join(rootDir, relSymlink)
Expect(utils.ResolveLink(fs, systemPath, rootDir, constants.MaxLinkDepth)).To(Equal(filepath.Join(rootDir, file)))
})
It("resolves a simple absolute symlink", func() {
systemPath := filepath.Join(rootDir, absSymlink)
Expect(utils.ResolveLink(fs, systemPath, rootDir, constants.MaxLinkDepth)).To(Equal(filepath.Join(rootDir, file)))
})
It("resolves some nested symlinks", func() {
systemPath := filepath.Join(rootDir, nestSymlink)
Expect(utils.ResolveLink(fs, systemPath, rootDir, constants.MaxLinkDepth)).To(Equal(filepath.Join(rootDir, file)))
})
It("does not resolve broken links", func() {
systemPath := filepath.Join(rootDir, brokenSymlink)
// Return the symlink path without resolving it
resolved, err := utils.ResolveLink(fs, systemPath, rootDir, constants.MaxLinkDepth)
Expect(resolved).To(Equal(systemPath))
Expect(err).To(HaveOccurred())
})
It("does not resolve too many levels of netsed links", func() {
systemPath := filepath.Join(rootDir, nestSymlink)
// Returns the symlink resolution up to the second level
resolved, err := utils.ResolveLink(fs, systemPath, rootDir, 2)
Expect(resolved).To(Equal(filepath.Join(rootDir, "/path/to/nest2nd")))
Expect(err).To(HaveOccurred())
})
})
Describe("FindFile", func() {
var rootDir, file1, file2, relSymlink string
BeforeEach(func() {
// The root directory
rootDir = "/some/root"
Expect(utils.MkdirAll(fs, rootDir, constants.DirPerm)).To(Succeed())
// Files to find
file1 = "/path/with/needle/findme.extension"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(file1)), constants.DirPerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(rootDir, file1), []byte("some data"), constants.FilePerm)).To(Succeed())
file2 = "/path/with/needle.aarch64/findme.ext"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(file2)), constants.DirPerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(rootDir, file2), []byte("some data"), constants.FilePerm)).To(Succeed())
// A symlink pointing to a relative path
relSymlink = "/path/to/symlink/pointing-to-file"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(relSymlink)), constants.DirPerm)).To(Succeed())
Expect(fs.Symlink("../../with/needle/findme.extension", filepath.Join(rootDir, relSymlink))).To(Succeed())
})
It("finds a matching file, first match wins file1", func() {
f, err := utils.FindFile(fs, rootDir, "/path/with/*dle*/*me.*", "/path/with/*aarch64/find*")
Expect(err).ShouldNot(HaveOccurred())
Expect(f).To(Equal(filepath.Join(rootDir, file1)))
})
It("finds a matching file, first match wins file2", func() {
f, err := utils.FindFile(fs, rootDir, "/path/with/*aarch64/find*", "/path/with/*dle*/*me.*")
Expect(err).ShouldNot(HaveOccurred())
Expect(f).To(Equal(filepath.Join(rootDir, file2)))
})
It("finds a matching file and resolves the link", func() {
f, err := utils.FindFile(fs, rootDir, "/path/*/symlink/pointing-to-*", "/path/with/*aarch64/find*")
Expect(err).ShouldNot(HaveOccurred())
Expect(f).To(Equal(filepath.Join(rootDir, file1)))
})
It("fails if there is no match", func() {
_, err := utils.FindFile(fs, rootDir, "/path/*/symlink/*no-match-*")
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("failed to find"))
})
It("fails on invalid parttern", func() {
_, err := utils.FindFile(fs, rootDir, "/path/*/symlink/badformat[]")
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("syntax error"))
})
})
Describe("FindFiles", func() {
var rootDir, file1, file2, file3, relSymlink string
BeforeEach(func() {
// The root directory
rootDir = "/some/root"
Expect(utils.MkdirAll(fs, rootDir, constants.DirPerm)).To(Succeed())
// Files to find
file1 = "/path/with/needle/findme.extension1"
file2 = "/path/with/needle/findme.extension2"
file3 = "/path/with/needle/hardtofindme"
Expect(utils.MkdirAll(fs, filepath.Join(rootDir, filepath.Dir(file1)), constants.DirPerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(rootDir, file1), []byte("file1"), constants.FilePerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(rootDir, file2), []byte("file2"), constants.FilePerm)).To(Succeed())
Expect(fs.WriteFile(filepath.Join(rootDir, file3), []byte("file3"), constants.FilePerm)).To(Succeed())
// A symlink pointing to a relative path
relSymlink = "/path/with/needle/findme.symlink"
Expect(fs.Symlink("hardtofindme", filepath.Join(rootDir, relSymlink))).To(Succeed())
})
It("finds all matching files", func() {
f, err := utils.FindFiles(fs, rootDir, "/path/with/*dle*/find*")
Expect(err).ShouldNot(HaveOccurred())
Expect(len(f)).To(Equal(3))
Expect(f).Should(ContainElement(filepath.Join(rootDir, file1)))
Expect(f).Should(ContainElement(filepath.Join(rootDir, file2)))
Expect(f).Should(ContainElement(filepath.Join(rootDir, file3)))
})
It("Returns empty list if there is no match", func() {
f, err := utils.FindFiles(fs, rootDir, "/path/with/needle/notthere*")
Expect(err).ShouldNot(HaveOccurred())
Expect(len(f)).Should(Equal(0))
})
})
Describe("FindKernel", Label("find"), func() {
BeforeEach(func() {
Expect(utils.MkdirAll(fs, "/path/boot", constants.DirPerm)).To(Succeed())
Expect(utils.MkdirAll(fs, "/path/lib/modules/5.3-31-def", constants.DirPerm)).To(Succeed())
_, err := fs.Create("/path/boot/vmlinuz-5.3-31-def")
Expect(err).ShouldNot(HaveOccurred())
})
It("finds kernel file and version", func() {
k, v, err := utils.FindKernel(fs, "/path")
Expect(err).ShouldNot(HaveOccurred())
Expect(k).To(Equal("/path/boot/vmlinuz-5.3-31-def"))
Expect(v).To(Equal("5.3-31-def"))
})
It("fails if no kernel is found", func() {
Expect(fs.RemoveAll("/path/boot/vmlinuz-5.3-31-def")).To(Succeed())
_, _, err := utils.FindKernelInitrd(fs, "/path")
Expect(err).Should(HaveOccurred())
})
It("fails if there is no /lib/modules", func() {
Expect(fs.RemoveAll("/path/lib/modules")).To(Succeed())
_, _, err := utils.FindKernelInitrd(fs, "/path")
Expect(err).Should(HaveOccurred())
})
It("fails if there is no kernel version in /lib/modules", func() {
Expect(fs.Remove("/path/boot/vmlinuz-5.3-31-def")).To(Succeed())
_, err := fs.Create("/path/boot/vmlinuz-6.3-31-higher")
Expect(err).ShouldNot(HaveOccurred())
_, _, err = utils.FindKernelInitrd(fs, "/path")
Expect(err).Should(HaveOccurred())
})
})
Describe("FindKernelInitrd", Label("find"), func() {
BeforeEach(func() {
Expect(utils.MkdirAll(fs, "/path/boot", constants.DirPerm)).To(Succeed())
Expect(utils.MkdirAll(fs, "/path/lib/modules/5.3-31-def", constants.DirPerm)).To(Succeed())
_, err := fs.Create("/path/boot/vmlinuz-5.3-31-def")
Expect(err).ShouldNot(HaveOccurred())
Expect(fs.Symlink("vmlinuz-5.3-31-def", "/path/boot/vmlinuz")).To(Succeed())
_, err = fs.Create("/path/boot/initrd")
Expect(err).ShouldNot(HaveOccurred())
})
It("finds kernel and initrd files", func() {
k, i, err := utils.FindKernelInitrd(fs, "/path")
Expect(err).ShouldNot(HaveOccurred())
Expect(k).To(Equal("/path/boot/vmlinuz-5.3-31-def"))
Expect(i).To(Equal("/path/boot/initrd"))
})
It("fails if no initrd is found", func() {
Expect(fs.Remove("/path/boot/initrd"))
_, _, err := utils.FindKernelInitrd(fs, "/path")
Expect(err).Should(HaveOccurred())
})
It("fails if no kernel is found", func() {
Expect(fs.Remove("/path/boot/vmlinuz-5.3-31-def"))
_, _, err := utils.FindKernelInitrd(fs, "/path")
Expect(err).Should(HaveOccurred())
})
})
Describe("CalcFileChecksum", Label("checksum"), func() {
It("compute correct sha256 checksum", func() {
testData := strings.Repeat("abcdefghilmnopqrstuvz\n", 20)
testDataSHA256 := "7f182529f6362ae9cfa952ab87342a7180db45d2c57b52b50a68b6130b15a422"
err := fs.Mkdir("/iso", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile("/iso/test.iso", []byte(testData), 0644)
Expect(err).ShouldNot(HaveOccurred())
checksum, err := utils.CalcFileChecksum(fs, "/iso/test.iso")
Expect(err).ShouldNot(HaveOccurred())
Expect(checksum).To(Equal(testDataSHA256))
})
})
Describe("CreateSquashFS", Label("CreateSquashFS"), func() {
It("runs with no options if none given", func() {
err := utils.CreateSquashFS(runner, logger, "source", "dest", []string{})
Expect(runner.IncludesCmds([][]string{
{"mksquashfs", "source", "dest"},
})).To(BeNil())
Expect(err).ToNot(HaveOccurred())
})
It("runs with options if given", func() {
err := utils.CreateSquashFS(runner, logger, "source", "dest", constants.GetDefaultSquashfsCompressionOptions())
cmd := []string{"mksquashfs", "source", "dest"}
cmd = append(cmd, constants.GetDefaultSquashfsCompressionOptions()...)
Expect(runner.IncludesCmds([][]string{
cmd,
})).To(BeNil())
Expect(err).ToNot(HaveOccurred())
})
It("ignores any '-e' option", func() {
args := append(constants.GetDefaultSquashfsCompressionOptions(), "-e /some/path")
err := utils.CreateSquashFS(runner, logger, "source", "dest", args)
cmd := []string{"mksquashfs", "source", "dest"}
cmd = append(cmd, constants.GetDefaultSquashfsCompressionOptions()...)
Expect(runner.IncludesCmds([][]string{
cmd,
})).To(BeNil())
Expect(err).ToNot(HaveOccurred())
})
It("excludes given paths", func() {
err := utils.CreateSquashFS(
runner, logger, "source", "dest", constants.GetDefaultSquashfsCompressionOptions(),
"some/path", "another/path",
)
cmd := []string{"mksquashfs", "source", "dest"}
cmd = append(cmd, constants.GetDefaultSquashfsCompressionOptions()...)
cmd = append(cmd, "-wildcards", "-e", "some/path", "another/path")
Expect(runner.IncludesCmds([][]string{
cmd,
})).To(Succeed())
Expect(err).ToNot(HaveOccurred())
})
It("returns an error if it fails", func() {
runner.ReturnError = errors.New("error")
err := utils.CreateSquashFS(runner, logger, "source", "dest", []string{})
Expect(runner.IncludesCmds([][]string{
{"mksquashfs", "source", "dest"},
})).To(BeNil())
Expect(err).To(HaveOccurred())
})
})
Describe("LoadEnvFile", Label("LoadEnvFile"), func() {
BeforeEach(func() {
fs.Mkdir("/etc", constants.DirPerm)
})
It("returns proper map if file exists", func() {
err := fs.WriteFile("/etc/envfile", []byte("TESTKEY=TESTVALUE"), constants.FilePerm)
Expect(err).ToNot(HaveOccurred())
envData, err := utils.LoadEnvFile(fs, "/etc/envfile")
Expect(err).ToNot(HaveOccurred())
Expect(envData).To(HaveKeyWithValue("TESTKEY", "TESTVALUE"))
})
It("returns error if file doesnt exist", func() {
_, err := utils.LoadEnvFile(fs, "/etc/envfile")
Expect(err).To(HaveOccurred())
})
It("returns error if it cant unmarshall the env file", func() {
err := fs.WriteFile("/etc/envfile", []byte("WHAT\"WHAT"), constants.FilePerm)
Expect(err).ToNot(HaveOccurred())
_, err = utils.LoadEnvFile(fs, "/etc/envfile")
Expect(err).To(HaveOccurred())
})
})
Describe("WriteEnvFile", func() {
BeforeEach(func() {
fs.Mkdir("/etc", constants.DirPerm)
})
It("writes new file and also updates values if the file exists", func() {
envData := map[string]string{
"TESTKEY": "TESTVALUE",
"TESTKEY2": "VALUE2",
}
err := utils.WriteEnvFile(fs, envData, "/etc/envfile")
Expect(err).ToNot(HaveOccurred())
loadedData, err := utils.LoadEnvFile(fs, "/etc/envfile")
Expect(err).ToNot(HaveOccurred())
Expect(loadedData).To(Equal(envData))
loadedData["TESTKEY2"] = "NEWVALUE"
loadedData["NEWKEY"] = "DATA"
utils.WriteEnvFile(fs, loadedData, "/etc/envfile")
envData, err = utils.LoadEnvFile(fs, "/etc/envfile")
Expect(err).NotTo(HaveOccurred())
Expect(envData).To(Equal(loadedData))
})
})
Describe("CleanStack", Label("CleanStack"), func() {
var cleaner *utils.CleanStack
BeforeEach(func() {
cleaner = utils.NewCleanStack()
})
It("Adds a callback to the stack and pops it", func() {
var flag bool
callback := func() error {
flag = true
return nil
}
Expect(cleaner.Pop()).To(BeNil())
cleaner.Push(callback)
poppedJob := cleaner.Pop()
Expect(poppedJob).NotTo(BeNil())
poppedJob.Run()
Expect(flag).To(BeTrue())
})
It("On Cleanup runs callback stack in reverse order", func() {
result := ""
callback1 := func() error {
result = result + "one "
return nil
}
callback2 := func() error {
result = result + "two "
return nil
}
callback3 := func() error {
result = result + "three "
return nil
}
callback4 := func() error {
result = result + "four "
return nil
}
callback5 := func() error {
result = result + "successOnly "
return nil
}
cleaner.Push(callback1)
cleaner.Push(callback2)
cleaner.Push(callback3)
cleaner.PushErrorOnly(callback4)
cleaner.PushSuccessOnly(callback5)
cleaner.Cleanup(nil)
// Fourth callback is not executed if no error is reported
Expect(result).To(Equal("successOnly three two one "))
})
It("cleans up keeping former error", func() {
err := errors.New("Former error")
count := 0
onErrorCallback := false
onSuccessCallback := false
callback := func() error {
count++
if count == 2 {
return errors.New("Cleanup Error")
}
return nil
}
successOnlyCallback := func() error {
onSuccessCallback = true
return nil
}
errorOnlyCallback := func() error {
onErrorCallback = true
return nil
}
cleaner.Push(callback)
cleaner.Push(callback)
cleaner.Push(callback)
cleaner.PushSuccessOnly(successOnlyCallback)
cleaner.PushErrorOnly(errorOnlyCallback)
err = cleaner.Cleanup(err)
Expect(count).To(Equal(3))
Expect(err.Error()).To(ContainSubstring("Former error"))
Expect(onSuccessCallback).To(BeFalse())
Expect(onErrorCallback).To(BeTrue())
})
It("On Cleanup error reports first error and all callbacks are executed", func() {
var err error
count := 0
onErrorCallback := false
onSuccessCallback := false
callback := func() error {
count++
if count >= 2 {
return errors.New(fmt.Sprintf("Cleanup error %d", count))
}
return nil
}
successOnlyCallback := func() error {
onSuccessCallback = true
return nil
}
errorOnlyCallback := func() error {
onErrorCallback = true
return nil
}
cleaner.PushSuccessOnly(successOnlyCallback)
cleaner.PushErrorOnly(errorOnlyCallback)
cleaner.Push(callback)
cleaner.Push(callback)
cleaner.Push(callback)
err = cleaner.Cleanup(err)
Expect(count).To(Equal(3))
Expect(err.Error()).To(ContainSubstring("Cleanup error 2"))
Expect(err.Error()).To(ContainSubstring("Cleanup error 3"))
Expect(onSuccessCallback).To(BeFalse())
Expect(onErrorCallback).To(BeTrue())
})
})
Describe("VHD utils", Label("vhd"), func() {
It("creates a valid header", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_RDONLY, constants.FilePerm)
info, _ := f.Stat()
// Should only have the footer in teh file, hence 512 bytes
Expect(info.Size()).To(BeNumerically("==", 512))
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
Expect(hex.EncodeToString(header.CreatorApplication[:])).To(Equal("656c656d"))
})
Describe("CHS calculation", func() {
It("limits the number of sectors", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// This size would make the chs calculation break, but we have a guard for it
f.Truncate(500 * 1024 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_RDONLY, constants.FilePerm)
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// cylinders which is (totalSectors / sectorsPerTrack) / heads
// and totalsectors is 65535 * 16 * 255 due to hitting the max sector
// This turns out to be 65535 or ffff in hex or [2]byte{255,255}
Expect(hex.EncodeToString(header.DiskGeometry[:2])).To(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(16))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(255))) // sectors per track
})
// The tests below test the different routes that the chs calculation can take to get the disk geometry
// it's all based on number of sectors, so we have to try with different known sizes to see if the
// geometry changes are properly reflected on the final VHD header
It("sets the disk geometry correctly based on sector number", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// one route of the chs calculation
f.Truncate(1 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_RDONLY, constants.FilePerm)
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// should not be the max value
Expect(hex.EncodeToString(header.DiskGeometry[:2])).ToNot(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(4))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(17))) // sectors per track
})
It("sets the disk geometry correctly based on sector number", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// one route of the chs calculation
f.Truncate(1 * 1024 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_RDONLY, constants.FilePerm)
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// should not be the max value
Expect(hex.EncodeToString(header.DiskGeometry[:2])).ToNot(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(16))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(63))) // sectors per track
})
It("sets the disk geometry correctly based on sector number", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// another route of the chs calculation
f.Truncate(220 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_RDONLY, constants.FilePerm)
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// should not be the max value
Expect(hex.EncodeToString(header.DiskGeometry[:2])).ToNot(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(16))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(31))) // sectors per track
})
})
})
})