github.com/vmware/govmomi@v0.37.1/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 }