package main import ( "bytes" "context" "flag" "fmt" "html/template" "log" "net/url" "os" "reflect" "strings" "time" _ "time/tzdata" "github.com/vmware/govmomi" "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" "github.com/PuerkitoBio/goquery" goconfluence "github.com/virtomize/confluence-go-api" ) var ( c *govmomi.Client ctx context.Context cancel context.CancelFunc sha1ver string // sha1 revision used to build the program buildTime string // when the executable was built busSharingResults []BusSharingResults location *time.Location ) type BusSharingResults struct { VmName string ClusterName string ControllerName string SharingType string } /* const ( // The virtual disk is not shared. VirtualDiskSharingSharingNone = VirtualDiskSharing("sharingNone") // The virtual disk is shared between multiple virtual machines. VirtualDiskSharingSharingMultiWriter = VirtualDiskSharing("sharingMultiWriter") ) */ // From https://stackoverflow.com/a/33769379 func Scan(d interface{}) { v := reflect.ValueOf(d) i := reflect.Indirect(v) s := i.Type() println(s.NumField()) // will print out 0, if you change Host to have 1 field, it prints out 1 } func query(t reflect.Type) { value := reflect.New(t).Interface() Scan(value) } // Thanks chatgpt // Refer also to https://github.com/vmware/govmomi/blob/v0.32.0/vim25/types/enum.go#L9704“ func sharedBusToString(sharedBus types.VirtualSCSISharing) string { switch sharedBus { case types.VirtualSCSISharingNoSharing: return "No Sharing" case types.VirtualSCSISharingPhysicalSharing: return "Physical" case types.VirtualSCSISharingVirtualSharing: return "Virtual" default: return "Unknown" } } func getScsiBusSharingVMs(client *govmomi.Client) error { var clusterName string ctx := context.Background() m := view.NewManager(client.Client) //f := find.NewFinder(client.Client, true) pc := property.DefaultCollector(client.Client) fmt.Printf("getScsiBusSharingVMs : Preparing views\n") // Get a view of all the VMs vms, err := m.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true) if err != nil { return err } defer vms.Destroy(ctx) // Get a view of all the hosts hs, err := m.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{"HostSystem"}, true) if err != nil { return err } defer hs.Destroy(ctx) // Retrieve all the VMs fmt.Printf("Getting VM listing\n") var vmList []mo.VirtualMachine err = vms.Retrieve(ctx, []string{"VirtualMachine"}, nil, &vmList) if err != nil { log.Printf("Error retrieving vm list : '%s'\n", err) return err } fmt.Printf("Found %d VMs\n", len(vmList)) // Retrieve all the hosts fmt.Printf("Getting host listing\n") var hsList []mo.HostSystem err = hs.Retrieve(ctx, []string{"HostSystem"}, nil, &hsList) if err != nil { log.Printf("Error retrieving hostsystem list : '%s'\n", err) return err } fmt.Printf("Found %d hosts\n", len(hsList)) // Iterate through VMs and check for SCSI bus sharing for _, vm := range vmList { //fmt.Printf("vm : %s [%s]\n", vm.Name, vm.Summary.Runtime.Host) //fmt.Printf("vm parent: %v\n", vm.Parent) /* // TODO : check for err // Get the object for this VM ref, _ := f.ObjectReference(ctx, vm.Entity().Self) ovm, _ := ref.(*object.VirtualMachine) // Get the resource pool and the owner of it pool, _ := ovm.ResourcePool(ctx) owner, _ := pool.Owner(ctx) fmt.Printf("owner: %v\n", owner) */ // Determine cluster based on runtime host of VM based on https://github.com/vmware/govmomi/issues/1242#issuecomment-427671990 for _, host := range hsList { if host.Reference() == *vm.Summary.Runtime.Host { //fmt.Printf("host %s matches with parent %s\n", host.Name, host.Parent) var cluster mo.ManagedEntity err = pc.RetrieveOne(ctx, *host.Parent, []string{"name"}, &cluster) if err != nil { log.Fatal(err) } //fmt.Println(cluster.Name) clusterName = cluster.Name break } } if len(vm.Config.Hardware.Device) > 0 { for _, device := range vm.Config.Hardware.Device { //fmt.Printf("device: %v\n", device) if scsi, ok := device.(types.BaseVirtualSCSIController); ok { //fmt.Printf("scsi: %v\n", scsi) controller := scsi.GetVirtualSCSIController() //fmt.Printf("controller: %s\n", device.GetVirtualDevice().DeviceInfo.GetDescription().Label) fmt.Printf("VM %s is using SCSI bus sharing mode: %s\n", vm.Name, string(controller.SharedBus)) if controller.SharedBus != "noSharing" { result := BusSharingResults{ VmName: vm.Name, ClusterName: clusterName, ControllerName: device.GetVirtualDevice().DeviceInfo.GetDescription().Label, SharingType: sharedBusToString(controller.SharedBus), } busSharingResults = append(busSharingResults, result) } } } } } return nil } func generateBusSharingTable() string { // Define the HTML template htmlTemplate := ` {{range .}} {{end}}
Vm Name Cluster Name Controller Name Sharing Type
{{.VmName}} {{.ClusterName}} {{.ControllerName}} {{.SharingType}}
` // Create a new template and parse the HTML template tmpl := template.Must(template.New("table").Parse(htmlTemplate)) // Create a buffer to store the HTML output var buf bytes.Buffer // Execute the template with the results and write to the buffer err := tmpl.Execute(&buf, busSharingResults) if err != nil { panic(err) } // Convert the buffer to a string htmlString := buf.String() fmt.Printf("generateBusSharingTable : %s\n", htmlString) return htmlString } func updateHtml(htmlContent string, heading string, newTable string) string { // Load the HTML content into a goquery document doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent)) if err != nil { fmt.Println(err) return htmlContent } // Find the h2 heading that matches our heading passed to the function doc.Find("h2").Each(func(i int, s *goquery.Selection) { if s.Text() == heading { // Find the table that follows this h2 heading table := s.NextFiltered("table") // Replace the table with a new table //newTableHTML := "
New Table Row 1
" newTable, _ := goquery.NewDocumentFromReader(strings.NewReader(newTable)) table.ReplaceWithSelection(newTable.Selection) } }) // Render the html h, err := doc.Html() if err != nil { fmt.Println(err) return htmlContent } // goquery produces implicit nodes when parsing the input html, remove them now fmt.Printf("updateHtml whole doc : %s\n", h) h = strings.Replace(h, "", "", -1) h = strings.Replace(h, "", "", -1) return h } func main() { // Command line flags vURL := flag.String("url", "", "The URL of a vCenter server, eg https://server.domain.example/sdk") vUser := flag.String("user", "", "The username to use when connecting to vCenter") vPass := flag.String("password", "", "The password to use when connecting to vCenter") vInsecure := flag.Bool("insecure", true, "Allow insecure connections to vCenter") vTZ := flag.String("tz", "Australia/Sydney", "The timezone to use when converting vCenter UTC times") cURL := flag.String("confluence-url", "https://confluence.yourdomain.com/wiki/rest/api", "The URL to your confluence rest API endpoint") cToken := flag.String("confluence-token", "", "Your Confluence Personal Access Token") cPageId := flag.String("confluence-pageid", "", "The page ID to update with the report") cSpaceKey := flag.String("confluence-spacekey", "HCS", "The confluence space key to use") //vmName := flag.String("vmname", "example-vm", "The vm to query metrics") //begin := flag.Duration("b", time.Hour, "Begin time") // default BeginTime is 1h ago flag.Parse() // Print logs to file f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening file: %v", err) } defer f.Close() log.SetOutput(f) fmt.Printf("Starting execution. Built on %s from sha1 %s\n", buildTime, sha1ver) // So we can convert vCenter UTC to our local timezone fmt.Printf("Setting timezone to '%s'\n", *vTZ) location, err = time.LoadLocation(*vTZ) if err != nil { fmt.Fprintf(os.Stderr, "Error setting timezone to %s : %s\n", *vTZ, err) os.Exit(1) } u, err := url.Parse(*vURL) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing url %s : %s\n", *vURL, err) os.Exit(1) } else { if !strings.HasSuffix(u.Path, "/sdk") { u.Path, _ = url.JoinPath(u.Path, "/sdk") log.Printf("Updated vCenter URL to '%v'\n", u) } } log.Printf("Connecting to vCenter %s\n", u) u.User = url.UserPassword(*vUser, *vPass) ctx, cancel = context.WithCancel(context.Background()) defer cancel() // Login to vcenter c, err = govmomi.NewClient(ctx, u, *vInsecure) if err != nil { fmt.Fprintf(os.Stderr, "Logging in error: %s\n", err) os.Exit(1) } defer c.Logout(ctx) // Find scsi bus sharing VMs err = getScsiBusSharingVMs(c) if err != nil { log.Printf("Error retrieving list of VMs with SCSI Bus Sharing : %s\n", err) return } // Connect to confluence fmt.Printf("Connecting to confluence %s\n", *cURL) api, err := goconfluence.NewAPI(*cURL, "", *cToken) if err != nil { fmt.Println(err) return } // get current user information /* currentUser, err := api.CurrentUser() if err != nil { fmt.Println(err) return } fmt.Printf("%+v\n", currentUser) */ // get content by content id c, err := api.GetContentByID(*cPageId, goconfluence.ContentQuery{ SpaceKey: *cSpaceKey, Expand: []string{"body.storage", "version"}, }) if err != nil { fmt.Println(err) return } fmt.Printf("%+v\n", c) fmt.Printf("Current version number : '%d'\n", c.Version.Number) // Generate new content for confluence //fmt.Printf("Current content: %s\n", c.Body.Storage.Value) //dummyTable := "
VM NameCluster Name
VM1Cluster1
" newTable := generateBusSharingTable() newContent := updateHtml(c.Body.Storage.Value, "wsdc-vc-npr.srv.westpac.com.au", newTable) fmt.Printf("New Content: %v\n", newContent) data := &goconfluence.Content{ ID: c.ID, Type: c.Type, Title: c.Title, Body: goconfluence.Body{ Storage: goconfluence.Storage{ Value: newContent, Representation: c.Body.Storage.Representation, }, }, Version: &goconfluence.Version{ Number: 3, }, Space: &goconfluence.Space{ Key: c.Space.Key, }, } //c.Body.Storage.Value = newContent fmt.Printf("confluence object : '%v'\n", data) result, err := api.UpdateContent(data) if err != nil { fmt.Printf("Error updating content: %s\n", err) return } fmt.Printf("result: %v\n", result) /* err = getScsiBusSharingVMs(c) if err != nil { log.Printf("Error retrieving list of VMs with SCSI Bus Sharing : %s\n", err) return } // Output final results in JSON if len(busSharingResults) > 0 { j, _ := json.Marshal(busSharingResults) fmt.Println(string(j)) } else { fmt.Println("{}") } */ }