diff --git a/test/assets/seed.img b/test/assets/seed.img new file mode 100644 index 00000000..6a978ed4 Binary files /dev/null and b/test/assets/seed.img differ diff --git a/test/osprovider.go b/test/osprovider.go index 4af16d27..240ee93e 100644 --- a/test/osprovider.go +++ b/test/osprovider.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/crc-org/vfkit/pkg/config" + "github.com/xi2/xz" "github.com/cavaliergopher/grab/v3" "github.com/crc-org/crc/v2/pkg/extract" @@ -42,6 +43,50 @@ func downloadPuipui(destDir string) ([]string, error) { return extract.Uncompress(resp.Filename, destDir) } +func downloadFedora(destDir string) (string, error) { + const fedoraVersion = "40" + arch := kernelArch() + release := "1.14" + buildString := fmt.Sprintf("%s-%s-%s", arch, fedoraVersion, release) + + var fedoraURL = fmt.Sprintf("https://download.fedoraproject.org/pub/fedora/linux/releases/%s/Cloud/%s/images/Fedora-Cloud-Base-AmazonEC2.%s.raw.xz", fedoraVersion, arch, buildString) + + // https://github.com/cavaliergopher/grab/issues/104 + grab.DefaultClient.UserAgent = "vfkit" + resp, err := grab.Get(destDir, fedoraURL) + if err != nil { + return "", err + } + return uncompressFedora(resp.Filename, destDir) +} + +func uncompressFedora(fileName string, targetDir string) (string, error) { + file, err := os.Open(filepath.Clean(fileName)) + if err != nil { + return "", err + } + defer file.Close() + + reader, err := xz.NewReader(file, 0) + if err != nil { + return "", err + } + + xzCutName, _ := strings.CutSuffix(filepath.Base(file.Name()), ".xz") + outPath := filepath.Join(targetDir, xzCutName) + out, err := os.Create(outPath) + if err != nil { + return "", err + } + + _, err = io.Copy(out, reader) + if err != nil { + return "", err + } + + return outPath, nil +} + type OsProvider interface { Fetch(destDir string) error ToVirtualMachine() (*config.VirtualMachine, error) @@ -64,6 +109,16 @@ func NewPuipuiProvider() *PuiPuiProvider { return &PuiPuiProvider{} } +type FedoraProvider struct { + diskImage string + efiVariableStorePath string + createVariableStore bool +} + +func NewFedoraProvider() *FedoraProvider { + return &FedoraProvider{} +} + func findFile(files []string, filename string) (string, error) { for _, f := range files { if filepath.Base(f) == filename { @@ -143,6 +198,18 @@ func (puipui *PuiPuiProvider) Fetch(destDir string) error { return nil } +func (fedora *FedoraProvider) Fetch(destDir string) error { + log.Infof("downloading fedora to %s", destDir) + file, err := downloadFedora(destDir) + if err != nil { + return err + } + + fedora.diskImage = file + + return nil +} + const puipuiMemoryMiB = 1 * 1024 const puipuiCPUs = 2 @@ -153,6 +220,13 @@ func (puipui *PuiPuiProvider) ToVirtualMachine() (*config.VirtualMachine, error) return vm, nil } +func (fedora *FedoraProvider) ToVirtualMachine() (*config.VirtualMachine, error) { + bootloader := config.NewEFIBootloader(fedora.efiVariableStorePath, fedora.createVariableStore) + vm := config.NewVirtualMachine(puipuiCPUs, puipuiMemoryMiB, bootloader) + + return vm, nil +} + func (puipui *PuiPuiProvider) SSHConfig() *ssh.ClientConfig { return &ssh.ClientConfig{ User: "root", @@ -174,3 +248,26 @@ func (puipui *PuiPuiProvider) SSHAccessMethods() []SSHAccessMethod { }, } } + +func (fedora *FedoraProvider) SSHConfig() *ssh.ClientConfig { + return &ssh.ClientConfig{ + User: "vfkituser", + Auth: []ssh.AuthMethod{ssh.Password("vfkittest")}, + // #nosec 106 -- the host SSH key of the VM will change each time it boots + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + +} + +func (fedora *FedoraProvider) SSHAccessMethods() []SSHAccessMethod { + return []SSHAccessMethod{ + { + network: "tcp", + port: 22, + }, + { + network: "vsock", + port: 2222, + }, + } +} diff --git a/test/vm_helpers.go b/test/vm_helpers.go index 5dc0bab8..4bce916b 100644 --- a/test/vm_helpers.go +++ b/test/vm_helpers.go @@ -215,7 +215,12 @@ func (vm *testVM) Start(t *testing.T) { } func (vm *testVM) Stop(t *testing.T) { - vm.SSHRun(t, "poweroff") + switch vm.provider.(type) { + case *FedoraProvider: + vm.SSHRun(t, "sudo shutdown now") + default: + vm.SSHRun(t, "poweroff") + } vm.vfkitCmd.Wait(t) } diff --git a/test/vm_test.go b/test/vm_test.go index 336ecea0..723c7f27 100644 --- a/test/vm_test.go +++ b/test/vm_test.go @@ -341,3 +341,63 @@ func checkPCIDevice(t *testing.T, vm *testVM, vendorID, deviceID int) { require.NoError(t, err) require.Regexp(t, re, string(lspci)) } + +func TestCloudInit(t *testing.T) { + if err := macOSAvailable(13); err != nil { + t.Log("Skipping TestCloudInit test") + return + } + fedoraProvider := NewFedoraProvider() + log.Info("fetching os image") + tempDir := t.TempDir() + err := fedoraProvider.Fetch(tempDir) + require.NoError(t, err) + + // set efi bootloader + fedoraProvider.efiVariableStorePath = "efi-variable-store" + fedoraProvider.createVariableStore = true + + vm := NewTestVM(t, fedoraProvider) + defer vm.Close(t) + require.NotNil(t, vm) + + vm.AddSSH(t, "tcp") + + // add vm image + dev1, err := config.VirtioBlkNew(fedoraProvider.diskImage) + require.NoError(t, err) + vm.AddDevice(t, dev1) + log.Infof("shared disk: %s - fedora", dev1.DevName) + + /* add cloud init config by using a premade ISO image + seed.img is an ISO image containing the user-data and meta-data file needed to configure the VM by cloud-init. + meta-data is an empty file + user-data has info about a new user that will be used to verify if the configuration has been applied. Its content is + ---- + #cloud-config + users: + - name: vfkituser + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + groups: users + plain_text_passwd: vfkittest + lock_passwd: false + ssh_pwauth: true + chpasswd: { expire: false } + */ + dev, err := config.VirtioBlkNew("assets/seed.img") + require.NoError(t, err) + vm.AddDevice(t, dev) + log.Infof("shared disk: %s - cloud-init", dev.DevName) + + vm.Start(t) + vm.WaitForSSH(t) + + data, err := vm.SSHCombinedOutput(t, "whoami") + require.NoError(t, err) + log.Infof("executed whoami - output: %s", string(data)) + require.Equal(t, "vfkituser\n", string(data)) + + log.Info("stopping vm") + vm.Stop(t) +}