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 }