// +build integration package vmwaretest import ( "errors" "fmt" "io/ioutil" "os" "path/filepath" "strings" // importing the packages registers these cli commands "github.com/vmware/govmomi/govc/cli" _ "github.com/vmware/govmomi/govc/datastore" _ "github.com/vmware/govmomi/govc/importx" _ "github.com/vmware/govmomi/govc/vm" _ "github.com/vmware/govmomi/govc/vm/guest" ) const WaitTimeout = 6000 // in seconds type AuthOptions struct { Host string Username string Password string Datacenter string Cluster string Network string Datastore string Folder string } func AuthOptionsFromEnv() (*AuthOptions, error) { host, hostExists := os.LookupEnv("GOVMOMI_URL") username, userExists := os.LookupEnv("GOVMOMI_USERNAME") password, pwdExists := os.LookupEnv("GOVMOMI_PASSWORD") datacenter, dcExists := os.LookupEnv("GOVMOMI_DATACENTER") cluster, clusterExists := os.LookupEnv("GOVMOMI_CLUSTER") network, netExists := os.LookupEnv("GOVMOMI_NETWORK") datastore, dsExists := os.LookupEnv("GOVMOMI_DATASTORE") folder, folderExists := os.LookupEnv("GOVMOMI_FOLDER") // If only one/two of them are not set, then fail if !hostExists { return nil, errors.New("GOVMOMI_URL not set") } if !userExists { return nil, errors.New("GOVMOMI_USERNAME not set") } if !pwdExists { return nil, errors.New("GOVMOMI_PASSWORD not set") } if !dcExists { return nil, errors.New("GOVMOMI_DATACENTER not set") } if !clusterExists { return nil, errors.New("GOVMOMI_CLUSTER not set") } if !netExists { return nil, errors.New("GOVMOMI_NETWORK not set") } if !dsExists { return nil, errors.New("GOVMOMI_DATASTORE not set") } if !folderExists { return nil, errors.New("GOVMOMI_FOLDER not set") } return &AuthOptions{ Host: host, Username: username, Password: password, Datacenter: datacenter, Cluster: cluster, Network: network, Datastore: datastore, Folder: folder, }, nil } func ImportImage(creds *AuthOptions, imagePath, imageName string) error { args := []string{ "import.vmdk", fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), "-k=true", fmt.Sprintf("-pool=%s/Resources", creds.Cluster), fmt.Sprintf("-dc=%s", creds.Datacenter), fmt.Sprintf("-ds=%s", creds.Datastore), imagePath, imageName, } retcode := cli.Run(args) if retcode != 0 { return errors.New("importing vmdk failed") } return nil } func DeleteImage(creds *AuthOptions, directoryName string) error { retcode := cli.Run([]string{ "datastore.rm", "-f=true", fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), "-k=true", fmt.Sprintf("-dc=%s", creds.Datacenter), fmt.Sprintf("-ds=%s", creds.Datastore), directoryName + "*", // because vm.create creates another directory with _1 prefix }) if retcode != 0 { return errors.New("deleting directory failed") } return nil } func runWithStdout(args []string) (string, int) { oldStdout := os.Stdout r, w, _ := os.Pipe() os.Stdout = w retcode := cli.Run(args) w.Close() out, _ := ioutil.ReadAll(r) os.Stdout = oldStdout return strings.TrimSpace(string(out)), retcode } func WithBootedImage(creds *AuthOptions, imagePath, imageName, publicKey string, f func(address string) error) (retErr error) { vmdkBaseName := filepath.Base(imagePath) args := []string{ "vm.create", fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), "-k=true", fmt.Sprintf("-pool=%s/Resources", creds.Cluster), fmt.Sprintf("-dc=%s", creds.Datacenter), fmt.Sprintf("-ds=%s", creds.Datastore), fmt.Sprintf("-folder=%s", creds.Folder), fmt.Sprintf("-net=%s", creds.Network), "-m=2048", "-g=rhel8_64Guest", "-on=true", "-firmware=bios", fmt.Sprintf("-disk=%s/%s", imageName, vmdkBaseName), "--disk.controller=ide", imageName, } retcode := cli.Run(args) if retcode != 0 { return errors.New("Creating VM from vmdk failed") } defer func() { args = []string{ "vm.destroy", fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), "-k=true", imageName, } retcode := cli.Run(args) if retcode != 0 { fmt.Printf("Deleting VM %s failed", imageName) return } }() // note: by default this will wait/block until an IP address is returned // note: using exec() instead of running the command b/c .Run() returns an int args = []string{ "vm.ip", fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), "-k=true", imageName, } ipAddress, retcode := runWithStdout(args) if retcode != 0 { return errors.New("Getting IP address for VM failed") } // Disabled b/c of https://github.com/vmware/govmomi/issues/2054 // upload public key on the VM //args = []string{ // "guest.mkdir", // fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), // "-k=true", // fmt.Sprintf("-vm=%s", imageName), // "-p", "/root/.ssh", //} //retcode = cli.Run(args) //if retcode != 0 { // return errors.New("mkdir /root/.ssh on VM failed") //} //args = []string{ // "guest.upload", // fmt.Sprintf("-u=%s:%s@%s", creds.Username, creds.Password, creds.Host), // "-k=true", // fmt.Sprintf("-vm=%s", imageName), // "-f=true", // publicKey, // this is a file path // "/root/.ssh/authorized_keys", //} //retcode = cli.Run(args) //if retcode != 0 { // return errors.New("Uploading public key to VM failed") //} return f(ipAddress) } // hard-coded SSH keys b/c we're having troubles uploading publicKey // to the VM, see https://github.com/vmware/govmomi/issues/2054 func WithSSHKeyPair(f func(privateKey, publicKey string) error) error { public := "/usr/share/tests/osbuild-composer/keyring/id_rsa.pub" private := "/usr/share/tests/osbuild-composer/keyring/id_rsa" return f(private, public) }