// Copyright 2024 WIT.COM Inc Licensed GPL 3.0 package virtigolib import ( "encoding/xml" "fmt" "os" "path/filepath" "reflect" "strings" "go.wit.com/log" "libvirt.org/go/libvirtxml" ) /* func makeStandardXml(hostname string) *libvirtxml.Domain { log.Info("create new xml file for:", hostname) domcfg := &libvirtxml.Domain{} addDefaultXml(domcfg, "standard.x86") addDefaultXml(domcfg, "memory") addDefaultXml(domcfg, "network") addDefaultXml(domcfg, "spice") addDefaultXml(domcfg, "qcow") return domcfg } */ func writeoutXml(domcfg *libvirtxml.Domain, filename string) error { xmldoc, err := domcfg.Marshal() if err != nil { fmt.Println("can't make xml file error:\n", err) return err } outfile := "/tmp/" + filename + ".xml" regfile, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE, 0666) if err != nil { return err } fmt.Fprintln(regfile, xmldoc) log.Info("File is in", outfile) regfile.Close() return nil } func setDiskFilename(domcfg *libvirtxml.Domain, filename string) { for i, x := range domcfg.Devices.Disks { // Create a new DomainDiskSourceFile struct newSource := &libvirtxml.DomainDiskSourceFile{ File: filename, // Set the file name here } // Assign it to the disk's source domcfg.Devices.Disks[i].Source.File = newSource // fmt.Printf("Disk Source %s\n", name) fmt.Printf("Disk Device %s\n", x.Source.File) } } func ReadXml(filename string) (*libvirtxml.Domain, error) { log.Verbose("parse xml file:", filename) hostname := filepath.Base(filename) hostname = strings.TrimSuffix(hostname, ".xml") pfile, err := os.ReadFile(filename) if err != nil { log.Println("ERROR:", err) return nil, err } domcfg := &libvirtxml.Domain{} err = domcfg.Unmarshal(string(pfile)) if err != nil { log.Info("Marshal failed on file", filename, err) return nil, err } if domcfg.Name != hostname { log.Info("ERROR: filename:", filename) log.Info("ERROR: domcfg.Name != name", domcfg.Name, hostname) log.Info("ERROR: xml filenames must match the xml name") os.Exit(-1) } return domcfg, nil } func mergeXml(domcfg *libvirtxml.Domain, filename string) error { log.Info("merge xml file:", filename) pfile, err := os.ReadFile(filename) if err != nil { log.Println("ERROR:", err) return err } err = domcfg.Unmarshal(string(pfile)) if err != nil { log.Info("Marshal failed on file", filename) return err } return nil } func setSimpleDisk(domcfg *libvirtxml.Domain, filename string) { // Clear out the existing disks (if any) domcfg.Devices.Disks = nil // Define a new disk with "mynew.qcow2" newDisk := libvirtxml.DomainDisk{ Device: "disk", Driver: &libvirtxml.DomainDiskDriver{ Name: "qemu", Type: "qcow2", }, Source: &libvirtxml.DomainDiskSource{ File: &libvirtxml.DomainDiskSourceFile{ File: filename, }, }, Target: &libvirtxml.DomainDiskTarget{ Dev: "vda", Bus: "virtio", }, } // Add the new disk to the domain configuration domcfg.Devices.Disks = append(domcfg.Devices.Disks, newDisk) } func getMacs(domcfg *libvirtxml.Domain) []string { var macs []string // Iterate over the network interfaces and print the MAC addresses for _, iface := range domcfg.Devices.Interfaces { if iface.MAC != nil { // iface.MAC.Address = "aa:bb:aa:bb:aa:ff" fmt.Printf("MAC Address: %+v\n", iface.MAC) // fmt.Printf("Interface: %s, MAC Address: %s\n", iface.Target.Dev, iface.MAC.Address) macs = append(macs, iface.MAC.Address) } else { fmt.Printf("Interface: %s, MAC Address: not available\n", iface.Target.Dev) } } return macs } // removes all the ethernet interfaces func clearEthernet(domcfg *libvirtxml.Domain) { // Clear out the existing disks (if any) domcfg.Devices.Interfaces = nil } // add a new ethernet interface with mac assigned to bridge name func addEthernetBridge(domcfg *libvirtxml.Domain, mac string, brname string) { // Define a new disk with "mynew.qcow2" // type DomainInterfaceType string var ib *libvirtxml.DomainInterfaceSourceBridge ib = new(libvirtxml.DomainInterfaceSourceBridge) ib.Bridge = brname newNet := libvirtxml.DomainInterface{ MAC: &libvirtxml.DomainInterfaceMAC{ Address: mac, }, Source: &libvirtxml.DomainInterfaceSource{ Bridge: ib, }, Model: &libvirtxml.DomainInterfaceModel{ Type: "virtio", }, /* this is for raw tap. use this for people who don't who don't have bridge groups or proper cluster backend networking literally leaving this blank makes the interface 'tap0' */ // Target: &libvirtxml.DomainInterfaceTarget{ // }, } // Add the new disk to the domain configuration domcfg.Devices.Interfaces = append(domcfg.Devices.Interfaces, newNet) } // makes an ethernet interface with qemu on dom0 as 'tapXXX' // doesn't require a bridge group or any changes to dom0 networking (probably) func addEthernetTap(domcfg *libvirtxml.Domain, mac string) { newNet := libvirtxml.DomainInterface{ MAC: &libvirtxml.DomainInterfaceMAC{ Address: mac, }, /* this is for raw tap. use this for people who don't who don't have bridge groups or proper cluster backend networking literally leaving this blank makes the interface 'tap0' */ Target: &libvirtxml.DomainInterfaceTarget{}, } // Add the new disk to the domain configuration domcfg.Devices.Interfaces = append(domcfg.Devices.Interfaces, newNet) } func setRandomMacs(domcfg *libvirtxml.Domain) { for i, x := range domcfg.Devices.Interfaces { // Create a new DomainDiskInterfaces struct newMac := &libvirtxml.DomainInterfaceMAC{ Address: "aa:bb:cc:dd:ee:ff", // make sure this is unique } // Assign it to the disk's source domcfg.Devices.Interfaces[i].MAC = newMac // fmt.Printf("Disk Source %s\n", name) // fmt.Printf("mac addr %+v\n", x.MAC) fmt.Printf("mac addr %s\n", x.MAC.Address) } } func xmlAny(a any) (string, error) { updatedXML, err := xml.MarshalIndent(a, "", " ") if err != nil { fmt.Printf("Failed to marshal updated XML: %v\n", err) return "", err } final := string(updatedXML) if final == "" { // everything seems to have been parsed pretty standard return "", nil } log.Info("Non-Standard XML Start") fmt.Println(final) log.Info("Non-Standard XML End") return final, nil } // dump out all the fields in libvirtxml.DomainDeviceList func DumpLibvirtxmlDomainNames() { var domain libvirtxml.Domain t := reflect.TypeOf(domain) fmt.Println("Fields in libvirtxml.Domain:") for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Println("Domain:", field.Name) } var device libvirtxml.DomainDeviceList t = reflect.TypeOf(device) fmt.Println("Fields in libvirtxml.DomainDeviceList:") for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Println("DomainDeviceList:", field.Name) } var iface libvirtxml.DomainInterface t = reflect.TypeOf(iface) fmt.Println("Fields in libvirtxml.DomainInterface:") for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Println("DomainInterface:", field.Name) } var isource libvirtxml.DomainInterfaceSource listFields(isource, "libvirtxml.DomainInterfaceSource") var ibridge libvirtxml.DomainInterfaceSourceBridge listFields(ibridge, "libvirtxml.DomainInterfaceSourceBridge") } func listFields(a any, name string) { t := reflect.TypeOf(a) fmt.Println("Fields in", name) for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Println(name, field.Name) } } // dump out all the fields in libvirtxml.DomainDeviceList func libvirtxmlDomainDevicesEmpty(mydom libvirtxml.DomainDeviceList) bool { var empty bool = true // Get the reflection object of the variable v := reflect.ValueOf(mydom) // Ensure that we are working with a struct if v.Kind() == reflect.Struct { // fmt.Println("Fields and values in libvirtxml.DomainDeviceList:") // Loop through each field in the struct for i := 0; i < v.NumField(); i++ { // Get field name field := v.Type().Field(i).Name // Get field value value := v.Field(i) if !value.IsValid() { fmt.Printf("Field: %s is nil or invalid\n", field) continue } // Check if the field is a string, array, or slice switch value.Kind() { case reflect.String: if value.String() != "" { fmt.Printf("Field: %s is a String with value: %s\n", field, value.String()) empty = false } case reflect.Slice: if value.Len() != 0 { fmt.Printf("Field: %s is a Slice with length: %d\n", field, value.Len()) empty = false } case reflect.Array: if value.Len() != 0 { fmt.Printf("Field: %s is an Array with length: %d\n", field, value.Len()) empty = false } case reflect.Ptr: if !value.IsValid() { fmt.Println("Field ptr: value:", value) fmt.Printf("Field ptr: %s is of type: %s\n", field, value.Kind()) empty = false } default: fmt.Printf("Field: %s is of type: %s\n", field, value.Kind()) empty = false } // Print the field name and value // fmt.Printf("Field: %s, Value: %v\n", field, value) } } else { fmt.Println("Provided variable is not a struct.") } return empty } // dump out all the fields in libvirtxml.DomainDeviceList func libvirtxmlDomainEmpty(mydom libvirtxml.Domain) bool { var empty bool = true // Get the reflection object of the variable v := reflect.ValueOf(mydom) // Ensure that we are working with a struct if v.Kind() == reflect.Struct { // fmt.Println("Fields and values in libvirtxml.DomainDeviceList:") // Loop through each field in the struct for i := 0; i < v.NumField(); i++ { // Get field name field := v.Type().Field(i).Name // Get field value value := v.Field(i) if !value.IsValid() { fmt.Printf("Field: %s is invalid\n", field) continue } // processed as Domain.Metadata & Domain.Resource // if (field == "IOThreads") || (field == "XMLName") { // fmt.Printf("Field: %s is: %s\n", field, value.String()) // continue // } // Check if the field is a string, array, or slice switch value.Kind() { case reflect.String: if value.String() != "" { fmt.Printf("Field: %s is a String with value: %s\n", field, value.String()) empty = false } case reflect.Slice: if value.Len() != 0 { fmt.Printf("Field: %s is a Slice with length: %d\n", field, value.Len()) empty = false } case reflect.Array: if value.Len() != 0 { fmt.Printf("Field: %s is an Array with length: %d\n", field, value.Len()) empty = false } case reflect.Struct: if IsStructEmptyOrNil(value) { // fmt.Printf("XML Field ignore empty Struct %s\n", field) } else { fmt.Printf("Field Struct is not empty %s is %+v\n", field, value) empty = false } case reflect.Uint: // probably ignore ints. when has that ever gone wrong? case reflect.Ptr: if value.IsValid() { if value.IsNil() { // this means the value is actually nil } else { // there is something still here in the libvirt XML fmt.Printf("Field Valid? field %s is of type: %s\n", field, value.Kind()) fmt.Println("Field Valid? ptr: value:", value) empty = false } } else { fmt.Println("Invalid Field ptr: value:", value) fmt.Printf("Invalid Field ptr: %s is of type: %s\n", field, value.Kind()) empty = false } default: fmt.Printf("Field: %s is of type: %s\n", field, value.Kind()) empty = false } // Print the field name and value // fmt.Printf("Field: %s, Value: %v\n", field, value) } } else { fmt.Println("Provided variable is not a struct.") } return empty } // IsStructEmptyOrNil checks if a struct or pointer to struct is empty, blank, or nil func IsStructEmptyOrNil(value interface{}) bool { val := reflect.ValueOf(value) // If the value is a pointer, check if it's nil and dereference it if not if val.Kind() == reflect.Ptr { if val.IsNil() { return true } val = val.Elem() } // Ensure we're dealing with a struct after potential dereferencing if val.Kind() != reflect.Struct { return false // Not a struct } // Check each field in the struct for its zero value for i := 0; i < val.NumField(); i++ { field := val.Field(i) // Skip unexported fields as we can't access them if !field.CanInterface() { continue } if !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) { return false // Found a non-zero field } } return true // All fields are zero values }