github.com/vmware/govmomi@v0.51.0/cli/object/save.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package object
     6  
     7  import (
     8  	"context"
     9  	"flag"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  
    15  	"github.com/vmware/govmomi/cli"
    16  	"github.com/vmware/govmomi/cli/flags"
    17  	"github.com/vmware/govmomi/fault"
    18  	"github.com/vmware/govmomi/property"
    19  	"github.com/vmware/govmomi/view"
    20  	"github.com/vmware/govmomi/vim25"
    21  	"github.com/vmware/govmomi/vim25/methods"
    22  	"github.com/vmware/govmomi/vim25/mo"
    23  	"github.com/vmware/govmomi/vim25/types"
    24  	"github.com/vmware/govmomi/vim25/xml"
    25  )
    26  
    27  type save struct {
    28  	*flags.FolderFlag
    29  
    30  	n       int
    31  	dir     string
    32  	force   bool
    33  	verbose bool
    34  	recurse bool
    35  	one     bool
    36  	license bool
    37  	kind    kinds
    38  	summary map[string]int
    39  }
    40  
    41  func init() {
    42  	cli.Register("object.save", &save{})
    43  }
    44  
    45  func (cmd *save) Register(ctx context.Context, f *flag.FlagSet) {
    46  	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
    47  	cmd.FolderFlag.Register(ctx, f)
    48  
    49  	f.BoolVar(&cmd.one, "1", false, "Save ROOT only, without its children")
    50  	f.StringVar(&cmd.dir, "d", "", "Save objects in directory")
    51  	f.BoolVar(&cmd.force, "f", false, "Remove existing object directory")
    52  	f.BoolVar(&cmd.license, "l", false, "Include license properties")
    53  	f.BoolVar(&cmd.recurse, "r", true, "Include children of the container view root")
    54  	f.Var(&cmd.kind, "type", "Resource types to save.  Defaults to all types")
    55  	f.BoolVar(&cmd.verbose, "v", false, "Verbose output")
    56  }
    57  
    58  func (cmd *save) Usage() string {
    59  	return "[PATH]"
    60  }
    61  
    62  func (cmd *save) Description() string {
    63  	return `Save managed objects.
    64  
    65  By default, the object tree and all properties are saved, starting at PATH.
    66  PATH defaults to ServiceContent, but can be specified to save a subset of objects.
    67  The primary use case for this command is to save inventory from a live vCenter and
    68  load it into a vcsim instance.
    69  
    70  Examples:
    71    govc object.save -d my-vcenter
    72    vcsim -load my-vcenter`
    73  }
    74  
    75  // write encodes data to file name
    76  func (cmd *save) write(name string, data any) error {
    77  	f, err := os.Create(filepath.Join(cmd.dir, name) + ".xml")
    78  	if err != nil {
    79  		return err
    80  	}
    81  	e := xml.NewEncoder(f)
    82  	e.Indent("", "  ")
    83  	if err = e.Encode(data); err != nil {
    84  		_ = f.Close()
    85  		return err
    86  	}
    87  	if err = f.Close(); err != nil {
    88  		return err
    89  	}
    90  	return nil
    91  }
    92  
    93  type saveMethod struct {
    94  	Name string
    95  	Data any
    96  }
    97  
    98  func saveDVS(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
    99  	res, err := methods.FetchDVPorts(ctx, c, &types.FetchDVPorts{This: ref})
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	return []saveMethod{{"FetchDVPorts", res}}, nil
   104  }
   105  
   106  func saveEnvironmentBrowser(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
   107  	var save []saveMethod
   108  	{
   109  		res, err := methods.QueryConfigOption(ctx, c, &types.QueryConfigOption{This: ref})
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  		save = append(save, saveMethod{"QueryConfigOption", res})
   114  	}
   115  	{
   116  		res, err := methods.QueryConfigTarget(ctx, c, &types.QueryConfigTarget{This: ref})
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		save = append(save, saveMethod{"QueryConfigTarget", res})
   121  	}
   122  	{
   123  		res, err := methods.QueryTargetCapabilities(ctx, c, &types.QueryTargetCapabilities{This: ref})
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  		save = append(save, saveMethod{"QueryTargetCapabilities", res})
   128  	}
   129  	return save, nil
   130  }
   131  
   132  func saveHostNetworkSystem(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
   133  	res, err := methods.QueryNetworkHint(ctx, c, &types.QueryNetworkHint{This: ref})
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return []saveMethod{{"QueryNetworkHint", res}}, nil
   138  }
   139  
   140  func saveHostSystem(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
   141  	res, err := methods.QueryTpmAttestationReport(ctx, c, &types.QueryTpmAttestationReport{This: ref})
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	return []saveMethod{{"QueryTpmAttestationReport", res}}, nil
   146  }
   147  
   148  func saveAlarmManager(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
   149  	res, err := methods.GetAlarm(ctx, c, &types.GetAlarm{This: ref})
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	pc := property.DefaultCollector(c)
   154  	var content []types.ObjectContent
   155  	if err = pc.Retrieve(ctx, res.Returnval, nil, &content); err != nil {
   156  		return nil, err
   157  	}
   158  	return []saveMethod{{"GetAlarm", res}, {"", content}}, nil
   159  }
   160  
   161  func saveLicenseAssignmentManager(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
   162  	res, err := methods.QueryAssignedLicenses(ctx, c, &types.QueryAssignedLicenses{This: ref})
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	return []saveMethod{{"QueryAssignedLicenses", res}}, nil
   167  }
   168  
   169  // saveObjects maps object types to functions that can save data that isn't available via the PropertyCollector
   170  var saveObjects = map[string]func(context.Context, *vim25.Client, types.ManagedObjectReference) ([]saveMethod, error){
   171  	"VmwareDistributedVirtualSwitch": saveDVS,
   172  	"EnvironmentBrowser":             saveEnvironmentBrowser,
   173  	"HostNetworkSystem":              saveHostNetworkSystem,
   174  	"HostSystem":                     saveHostSystem,
   175  	"AlarmManager":                   saveAlarmManager,
   176  	"LicenseAssignmentManager":       saveLicenseAssignmentManager,
   177  }
   178  
   179  func (cmd *save) save(content []types.ObjectContent) error {
   180  	for _, x := range content {
   181  		x.MissingSet = nil // drop any NoPermission faults
   182  		cmd.summary[x.Obj.Type]++
   183  		if cmd.verbose {
   184  			fmt.Printf("Saving %s...", x.Obj)
   185  		}
   186  		ref := x.Obj.Encode()
   187  		name := fmt.Sprintf("%04d-%s", cmd.n, ref)
   188  		cmd.n++
   189  		if err := cmd.write(name, x); err != nil {
   190  			return err
   191  		}
   192  		if cmd.verbose {
   193  			fmt.Println("ok")
   194  		}
   195  
   196  		c, _ := cmd.Client()
   197  		if method, ok := saveObjects[x.Obj.Type]; ok {
   198  			objs, err := method(context.Background(), c, x.Obj)
   199  			if err != nil {
   200  				if fault.Is(err, &types.HostNotConnected{}) {
   201  					continue
   202  				}
   203  				if fault.Is(err, &types.NotSupported{}) {
   204  					continue
   205  				}
   206  				return err
   207  			}
   208  			dir := filepath.Join(cmd.dir, ref)
   209  			if err = os.MkdirAll(dir, 0755); err != nil {
   210  				return err
   211  			}
   212  			for _, obj := range objs {
   213  				if obj.Name == "" {
   214  					err = cmd.save(obj.Data.([]types.ObjectContent))
   215  					if err != nil {
   216  						return err
   217  					}
   218  					continue
   219  				}
   220  				err = cmd.write(filepath.Join(ref, obj.Name), obj.Data)
   221  				if err != nil {
   222  					return err
   223  				}
   224  			}
   225  		}
   226  	}
   227  	return nil
   228  }
   229  
   230  func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
   231  	if f.NArg() > 1 {
   232  		return flag.ErrHelp
   233  	}
   234  
   235  	cmd.summary = make(map[string]int)
   236  	c, err := cmd.Client()
   237  	if err != nil {
   238  		return err
   239  	}
   240  	if cmd.dir == "" {
   241  		u := c.URL()
   242  		name := u.Fragment
   243  		if name == "" {
   244  			name = u.Hostname()
   245  		}
   246  		cmd.dir = "vcsim-" + name
   247  	}
   248  	mkdir := os.Mkdir
   249  	if cmd.force {
   250  		mkdir = os.MkdirAll
   251  	}
   252  	if err := mkdir(cmd.dir, 0755); err != nil {
   253  		return err
   254  	}
   255  
   256  	var content []types.ObjectContent
   257  	pc := property.DefaultCollector(c)
   258  	root := vim25.ServiceInstance
   259  	if f.NArg() == 1 {
   260  		root, err = cmd.ManagedObject(ctx, f.Arg(0))
   261  		if err != nil {
   262  			if !root.FromString(f.Arg(0)) {
   263  				return err
   264  			}
   265  		}
   266  		if cmd.one {
   267  			err = pc.RetrieveOne(ctx, root, nil, &content)
   268  			if err != nil {
   269  				return nil
   270  			}
   271  			if err = cmd.save(content); err != nil {
   272  				return err
   273  			}
   274  			return nil
   275  		}
   276  	}
   277  
   278  	req := types.RetrievePropertiesEx{
   279  		This:    pc.Reference(),
   280  		Options: types.RetrieveOptions{MaxObjects: 10},
   281  	}
   282  
   283  	if root == vim25.ServiceInstance {
   284  		err := pc.RetrieveOne(ctx, root, []string{"content"}, &content)
   285  		if err != nil {
   286  			return nil
   287  		}
   288  		if err = cmd.save(content); err != nil {
   289  			return err
   290  		}
   291  		if cmd.one {
   292  			return nil
   293  		}
   294  
   295  		root = c.ServiceContent.RootFolder
   296  
   297  		for _, p := range content[0].PropSet {
   298  			if c, ok := p.Val.(types.ServiceContent); ok {
   299  				var path []string
   300  				var selectSet []types.BaseSelectionSpec
   301  				var propSet []types.PropertySpec
   302  				for _, ref := range mo.References(c) {
   303  					all := types.NewBool(true)
   304  					switch ref.Type {
   305  					case "LicenseManager":
   306  						selectSet = []types.BaseSelectionSpec{&types.TraversalSpec{
   307  							Type: ref.Type,
   308  							Path: "licenseAssignmentManager",
   309  						}}
   310  						propSet = []types.PropertySpec{{Type: "LicenseAssignmentManager", All: all}}
   311  						// avoid saving "licenses" property by default as it includes the keys
   312  						if cmd.license == false {
   313  							path = []string{selectSet[0].(*types.TraversalSpec).Path}
   314  							all, selectSet, propSet = nil, nil, nil
   315  						}
   316  					case "ServiceManager":
   317  						all = nil
   318  					}
   319  					req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
   320  						ObjectSet: []types.ObjectSpec{{
   321  							Obj:       ref,
   322  							SelectSet: selectSet,
   323  						}},
   324  						PropSet: append(propSet, types.PropertySpec{
   325  							Type:    ref.Type,
   326  							All:     all,
   327  							PathSet: path,
   328  						}),
   329  					})
   330  				}
   331  				break
   332  			}
   333  		}
   334  	}
   335  
   336  	m := view.NewManager(c)
   337  	v, err := m.CreateContainerView(ctx, root, cmd.kind, cmd.recurse)
   338  	if err != nil {
   339  		return err
   340  	}
   341  
   342  	defer func() {
   343  		_ = v.Destroy(ctx)
   344  	}()
   345  
   346  	all := types.NewBool(true)
   347  	req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
   348  		ObjectSet: []types.ObjectSpec{{
   349  			Obj:  v.Reference(),
   350  			Skip: types.NewBool(false),
   351  			SelectSet: []types.BaseSelectionSpec{
   352  				&types.TraversalSpec{
   353  					Type: v.Reference().Type,
   354  					Path: "view",
   355  					SelectSet: []types.BaseSelectionSpec{
   356  						&types.SelectionSpec{
   357  							Name: "computeTraversalSpec",
   358  						},
   359  						&types.SelectionSpec{
   360  							Name: "datastoreTraversalSpec",
   361  						},
   362  						&types.SelectionSpec{
   363  							Name: "hostDatastoreSystemTraversalSpec",
   364  						},
   365  						&types.SelectionSpec{
   366  							Name: "hostNetworkSystemTraversalSpec",
   367  						},
   368  						&types.SelectionSpec{
   369  							Name: "hostVirtualNicManagerTraversalSpec",
   370  						},
   371  						&types.SelectionSpec{
   372  							Name: "hostCertificateManagerTraversalSpec",
   373  						},
   374  						&types.SelectionSpec{
   375  							Name: "entityTraversalSpec",
   376  						},
   377  					},
   378  				},
   379  				&types.TraversalSpec{
   380  					SelectionSpec: types.SelectionSpec{
   381  						Name: "computeTraversalSpec",
   382  					},
   383  					Type: "ComputeResource",
   384  					Path: "environmentBrowser",
   385  				},
   386  				&types.TraversalSpec{
   387  					SelectionSpec: types.SelectionSpec{
   388  						Name: "datastoreTraversalSpec",
   389  					},
   390  					Type: "Datastore",
   391  					Path: "browser",
   392  				},
   393  				&types.TraversalSpec{
   394  					SelectionSpec: types.SelectionSpec{
   395  						Name: "hostNetworkSystemTraversalSpec",
   396  					},
   397  					Type: "HostSystem",
   398  					Path: "configManager.networkSystem",
   399  				},
   400  				&types.TraversalSpec{
   401  					SelectionSpec: types.SelectionSpec{
   402  						Name: "hostVirtualNicManagerTraversalSpec",
   403  					},
   404  					Type: "HostSystem",
   405  					Path: "configManager.virtualNicManager",
   406  				},
   407  				&types.TraversalSpec{
   408  					SelectionSpec: types.SelectionSpec{
   409  						Name: "hostCertificateManagerTraversalSpec",
   410  					},
   411  					Type: "HostSystem",
   412  					Path: "configManager.certificateManager",
   413  				},
   414  				&types.TraversalSpec{
   415  					SelectionSpec: types.SelectionSpec{
   416  						Name: "hostDatastoreSystemTraversalSpec",
   417  					},
   418  					Type: "HostSystem",
   419  					Path: "configManager.datastoreSystem",
   420  				},
   421  				&types.TraversalSpec{
   422  					SelectionSpec: types.SelectionSpec{
   423  						Name: "entityTraversalSpec",
   424  					},
   425  					Type: "ManagedEntity",
   426  					Path: "recentTask",
   427  				},
   428  			},
   429  		}},
   430  		PropSet: []types.PropertySpec{
   431  			{Type: "EnvironmentBrowser", All: all},
   432  			{Type: "HostDatastoreBrowser", All: all},
   433  			{Type: "HostDatastoreSystem", All: all},
   434  			{Type: "HostNetworkSystem", All: all},
   435  			{Type: "HostVirtualNicManager", All: all},
   436  			{Type: "HostCertificateManager", All: all},
   437  			{Type: "ManagedEntity", All: all},
   438  			{Type: "Task", All: all},
   439  		},
   440  	})
   441  
   442  	res, err := methods.RetrievePropertiesEx(ctx, c, &req)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	if err = cmd.save(res.Returnval.Objects); err != nil {
   447  		return err
   448  	}
   449  
   450  	token := res.Returnval.Token
   451  	for token != "" {
   452  		cres, err := methods.ContinueRetrievePropertiesEx(ctx, c, &types.ContinueRetrievePropertiesEx{
   453  			This:  req.This,
   454  			Token: token,
   455  		})
   456  		if err != nil {
   457  			return err
   458  		}
   459  		token = cres.Returnval.Token
   460  		if err = cmd.save(cres.Returnval.Objects); err != nil {
   461  			return err
   462  		}
   463  	}
   464  
   465  	var summary []string
   466  	for k, v := range cmd.summary {
   467  		if v == 1 && !cmd.verbose {
   468  			continue
   469  		}
   470  		summary = append(summary, fmt.Sprintf("%s: %d", k, v))
   471  	}
   472  	sort.Strings(summary)
   473  
   474  	s := ", including"
   475  	if cmd.verbose {
   476  		s = ""
   477  	}
   478  	fmt.Printf("Saved %d total objects to %q%s:\n", cmd.n, cmd.dir, s)
   479  	for i := range summary {
   480  		fmt.Println(summary[i])
   481  	}
   482  
   483  	return nil
   484  }