github.com/vmware/govmomi@v0.37.2/govc/object/save.go (about)

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