diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3988c72 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module orphanreport + +go 1.21.6 + +require github.com/vmware/govmomi v0.34.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2f07e5b --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vmware/govmomi v0.34.2 h1:o6ydkTVITOkpQU6HAf6tP5GvHFCNJlNUNlMsvFK77X4= +github.com/vmware/govmomi v0.34.2/go.mod h1:qWWT6n9mdCr/T9vySsoUqcI04sSEj4CqHXxtk/Y+Los= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..6d1d449 --- /dev/null +++ b/main.go @@ -0,0 +1,238 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net/url" + "os" + "strings" + "time" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/view" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +var ( + c *govmomi.Client + ctx context.Context + cancel context.CancelFunc + location *time.Location + sha1ver string // sha1 revision used to build the program + buildTime string // when the executable was built +) + +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") + vTZ := flag.String("tz", "Australia/Sydney", "The timezone to use when converting vCenter UTC times") + vInsecure := flag.Bool("insecure", true, "Allow insecure connections to vCenter") + //begin := flag.Duration("b", time.Hour, "Begin time") // default BeginTime is 1h ago + flag.Parse() + + var err error + 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) + } + } + + fmt.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) + + // TODO : Refactor to iterate through all datacenters + /* + // Create a finder + finder := find.NewFinder(c.Client, true) + // Find one and only datacenter, not sure how VMware linked mode will work + dc, err := finder.DatacenterOrDefault(ctx, "") + if err != nil { + fmt.Printf("No Datacenter instance could be found inside of vCenter : '%v'\n", err) + } + fmt.Printf("Looking at datacenter %s\n", dc.Name()) + + // Make future calls local to this datacenter + finder.SetDatacenter(dc) + + // Find all datastores + datastores, err := finder.DatastoreList(ctx, "*") + if err != nil { + fmt.Printf("DatastoreList err: %v\n", err) + } + + // Retrieve ManagedObjectReference for each datastore + var datastoreRefs []types.ManagedObjectReference + for _, ds := range datastores { + ref := ds.Reference() + datastoreRefs = append(datastoreRefs, ref) + } + */ + + datastoreRefs, err := getDatastoreRefs(ctx, c) + if err != nil { + fmt.Printf("err: %v\n", err) + } + + for i, val := range datastoreRefs { + fmt.Printf("val %d: %v\n", i, val) + } + + /* + // Find a host + hostObj, err := finder.DefaultHostSystem(ctx) + + if err != nil { + fmt.Println("Failed to find host:", err) + return + } + + // Find all datastores on the host + datastores, err := hostObj.Datastores(ctx) + + if err != nil { + fmt.Println("Failed to retrieve datastores:", err) + return + } + + // Iterate over each datastore + + for _, ds := range datastores { + fmt.Println("Datastore:", ds.Name()) + + // Get the host's datastore browser + hostBrowser, err := hostObj.ConfigManager().DatastoreBrowser(ctx) + if err != nil { + fmt.Printf("Failed to get datastore browser for datastore %s: %v\n", ds.Name(), err) + continue + } + + // Recursively list files and folders in the datastore + dsPath := "[" + ds.Name() + "]" + err = listFilesRecursively(ctx, hostBrowser, dsPath, ds.Name()) + if err != nil { + fmt.Printf("Failed to list files in datastore %s: %v\n", ds.Name(), err) + continue + } + + } + */ +} + +// getDatastoreRefs returns a list of ManagedObjectReference objects corresponding to every datastore in the vCenter. +func getDatastoreRefs(ctx context.Context, client *govmomi.Client) ([]types.ManagedObjectReference, error) { + var results []types.ManagedObjectReference + m := view.NewManager(client.Client) + view, err := m.CreateContainerView(ctx, client.ServiceContent.RootFolder, []string{"Datastore"}, true) + if err != nil { + return nil, fmt.Errorf("failed to create view: %v", err) + } + defer view.Destroy(ctx) + + var refs []mo.Datastore + err = view.Retrieve(ctx, []string{"Datastore"}, []string{"name", "summary"}, &refs) + if err != nil { + return nil, fmt.Errorf("failed to retrieve datastores: %v", err) + } + + for i := range refs { + //fmt.Printf("refs[i]: %v\n", refs[i]) + fmt.Printf("Datastore '%s', Shared : '%t'\n", refs[i].Name, *refs[i].Summary.MultipleHostAccess) + results = append(results, refs[i].Reference()) + } + + return results, nil +} + +/* +func listFilesRecursively(ctx context.Context, browser *host.DatastoreBrowser, path string, datastoreName string) error { + searchResult, err := browser.SearchDatastoreSubFolders(ctx, path, "*", true) + if err != nil { + return err + } + + for _, file := range searchResult.File { + fmt.Printf("File: %s\n", file.Path) + } + for _, folder := range searchResult.Folder { + fmt.Printf("Folder: %s\n", folder.Path) + err := listFilesRecursively(ctx, browser, folder.Path, datastoreName) + if err != nil { + return err + } + } + + return nil +} +*/ + +/* +func listFilesRecursively(ctx context.Context, browser *object.HostDatastoreBrowser, path string, datastoreName string) error { + var files []mo.FileInfo + err := browser.List(ctx, path, &files) + if err != nil { + return err + } + + // Get list of VMs on the datastore + var vms []mo.VirtualMachine + browser. + err = browser.Datastore().VirtualMachines(ctx, &vms) + if err != nil { + return err + } + + // Create a map to store VM file paths + vmFilePaths := make(map[string]bool) + for _, vm := range vms { + for _, filePath := range vm.LayoutEx.File { + vmFilePaths[filePath.Name] = true + } + } + + // Compare file list with VM file paths + for _, fileInfo := range files { + if !vmFilePaths[fileInfo.Path] { + fmt.Printf("File %s on datastore %s is not owned by any VM\n", fileInfo.Path, datastoreName) + } + if fileInfo.IsDir { + err := listFilesRecursively(ctx, browser, fileInfo.Path, datastoreName) + if err != nil { + return err + } + } + } + return nil +} +*/