package e2e_test import ( "context" "fmt" "net" "os" "os/exec" "path" "strconv" "testing" "github.com/google/uuid" process "github.com/mudler/go-processmanager" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/spectrocloud/peg/matcher" machine "github.com/spectrocloud/peg/pkg/machine" "github.com/spectrocloud/peg/pkg/machine/types" ) func TestE2e(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "kcrypt-challenger e2e test Suite") } func user() string { user := os.Getenv("SSH_USER") if user == "" { user = "kairos" } return user } func pass() string { pass := os.Getenv("SSH_PASS") if pass == "" { pass = "kairos" } return pass } func startVM() VM { if os.Getenv("ISO") == "" { fmt.Println("ISO missing") os.Exit(1) } vmName := uuid.New().String() stateDir, err := os.MkdirTemp("", "") Expect(err).ToNot(HaveOccurred()) emulateTPM(stateDir) sshPort, err := getFreePort() Expect(err).ToNot(HaveOccurred()) memory := os.Getenv("MEMORY") if memory == "" { memory = "2096" } cpus := os.Getenv("CPUS") if cpus == "" { cpus = "2" } opts := []types.MachineOption{ types.QEMUEngine, types.WithISO(os.Getenv("ISO")), types.WithMemory(memory), types.WithCPU(cpus), types.WithSSHPort(strconv.Itoa(sshPort)), types.WithID(vmName), types.WithSSHUser(user()), types.WithSSHPass(pass()), types.OnFailure(func(p *process.Process) { out, _ := os.ReadFile(p.StdoutPath()) err, _ := os.ReadFile(p.StderrPath()) status, _ := p.ExitCode() // We are explicitly killing the qemu process. We don't treat that as an error // but we just print the output just in case. fmt.Printf("\nVM Aborted: %s %s Exit status: %s\n", out, err, status) }), types.WithStateDir(stateDir), func(m *types.MachineConfig) error { m.Args = append(m.Args, "-chardev", fmt.Sprintf("socket,id=chrtpm,path=%s/swtpm-sock", path.Join(stateDir, "tpm")), "-tpmdev", "emulator,id=tpm0,chardev=chrtpm", "-device", "tpm-tis,tpmdev=tpm0", ) return nil }, } // Set this to true to debug. // You can connect to it with "spicy" or other tool. var spicePort int if os.Getenv("MACHINE_SPICY") != "" { spicePort, err = getFreePort() Expect(err).ToNot(HaveOccurred()) fmt.Printf("Spice port = %d\n", spicePort) opts = append(opts, types.WithDisplay(fmt.Sprintf("-spice port=%d,addr=127.0.0.1,disable-ticketing", spicePort))) } if os.Getenv("KVM") != "" { opts = append(opts, func(m *types.MachineConfig) error { m.Args = append(m.Args, "-enable-kvm", ) return nil }) } m, err := machine.New(opts...) Expect(err).ToNot(HaveOccurred()) vm := NewVM(m, stateDir) err = vm.Start(context.Background()) Expect(err).ToNot(HaveOccurred()) if os.Getenv("MACHINE_SPICY") != "" { cmd := exec.Command("spicy", "-h", "127.0.0.1", "-p", strconv.Itoa(spicePort)) err = cmd.Start() Expect(err).ToNot(HaveOccurred()) } return vm } // return the PID of the swtpm (to be killed later) and the state directory func emulateTPM(stateDir string) { t := path.Join(stateDir, "tpm") err := os.MkdirAll(t, os.ModePerm) Expect(err).ToNot(HaveOccurred()) cmd := exec.Command("swtpm", "socket", "--tpmstate", fmt.Sprintf("dir=%s", t), "--ctrl", fmt.Sprintf("type=unixio,path=%s/swtpm-sock", t), "--tpm2", "--log", "level=20") err = cmd.Start() Expect(err).ToNot(HaveOccurred()) err = os.WriteFile(path.Join(t, "pid"), []byte(strconv.Itoa(cmd.Process.Pid)), 0744) Expect(err).ToNot(HaveOccurred()) } // https://gist.github.com/sevkin/96bdae9274465b2d09191384f86ef39d // GetFreePort asks the kernel for a free open port that is ready to use. func getFreePort() (port int, err error) { var a *net.TCPAddr if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { var l *net.TCPListener if l, err = net.ListenTCP("tcp", a); err == nil { defer l.Close() return l.Addr().(*net.TCPAddr).Port, nil } } return }