1/* 2Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package importx 18 19import ( 20 "context" 21 "errors" 22 "flag" 23 "fmt" 24 "path" 25 26 "github.com/vmware/govmomi/govc/cli" 27 "github.com/vmware/govmomi/govc/flags" 28 "github.com/vmware/govmomi/nfc" 29 "github.com/vmware/govmomi/object" 30 "github.com/vmware/govmomi/ovf" 31 "github.com/vmware/govmomi/vim25" 32 "github.com/vmware/govmomi/vim25/soap" 33 "github.com/vmware/govmomi/vim25/types" 34) 35 36type ovfx struct { 37 *flags.DatastoreFlag 38 *flags.HostSystemFlag 39 *flags.OutputFlag 40 *flags.ResourcePoolFlag 41 *flags.FolderFlag 42 43 *ArchiveFlag 44 *OptionsFlag 45 46 Name string 47 48 Client *vim25.Client 49 Datacenter *object.Datacenter 50 Datastore *object.Datastore 51 ResourcePool *object.ResourcePool 52} 53 54func init() { 55 cli.Register("import.ovf", &ovfx{}) 56} 57 58func (cmd *ovfx) Register(ctx context.Context, f *flag.FlagSet) { 59 cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) 60 cmd.DatastoreFlag.Register(ctx, f) 61 cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx) 62 cmd.HostSystemFlag.Register(ctx, f) 63 cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) 64 cmd.OutputFlag.Register(ctx, f) 65 cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) 66 cmd.ResourcePoolFlag.Register(ctx, f) 67 cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) 68 cmd.FolderFlag.Register(ctx, f) 69 70 cmd.ArchiveFlag, ctx = newArchiveFlag(ctx) 71 cmd.ArchiveFlag.Register(ctx, f) 72 cmd.OptionsFlag, ctx = newOptionsFlag(ctx) 73 cmd.OptionsFlag.Register(ctx, f) 74 75 f.StringVar(&cmd.Name, "name", "", "Name to use for new entity") 76} 77 78func (cmd *ovfx) Process(ctx context.Context) error { 79 if err := cmd.DatastoreFlag.Process(ctx); err != nil { 80 return err 81 } 82 if err := cmd.HostSystemFlag.Process(ctx); err != nil { 83 return err 84 } 85 if err := cmd.OutputFlag.Process(ctx); err != nil { 86 return err 87 } 88 if err := cmd.ResourcePoolFlag.Process(ctx); err != nil { 89 return err 90 } 91 if err := cmd.ArchiveFlag.Process(ctx); err != nil { 92 return err 93 } 94 if err := cmd.OptionsFlag.Process(ctx); err != nil { 95 return err 96 } 97 if err := cmd.FolderFlag.Process(ctx); err != nil { 98 return err 99 } 100 return nil 101} 102 103func (cmd *ovfx) Usage() string { 104 return "PATH_TO_OVF" 105} 106 107func (cmd *ovfx) Run(ctx context.Context, f *flag.FlagSet) error { 108 fpath, err := cmd.Prepare(f) 109 if err != nil { 110 return err 111 } 112 113 archive := &FileArchive{path: fpath} 114 archive.Client = cmd.Client 115 116 cmd.Archive = archive 117 118 moref, err := cmd.Import(fpath) 119 if err != nil { 120 return err 121 } 122 123 vm := object.NewVirtualMachine(cmd.Client, *moref) 124 return cmd.Deploy(vm) 125} 126 127func (cmd *ovfx) Prepare(f *flag.FlagSet) (string, error) { 128 var err error 129 130 args := f.Args() 131 if len(args) != 1 { 132 return "", errors.New("no file specified") 133 } 134 135 cmd.Client, err = cmd.DatastoreFlag.Client() 136 if err != nil { 137 return "", err 138 } 139 140 cmd.Datacenter, err = cmd.DatastoreFlag.Datacenter() 141 if err != nil { 142 return "", err 143 } 144 145 cmd.Datastore, err = cmd.DatastoreFlag.Datastore() 146 if err != nil { 147 return "", err 148 } 149 150 cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePoolIfSpecified() 151 if err != nil { 152 return "", err 153 } 154 155 return f.Arg(0), nil 156} 157 158func (cmd *ovfx) Deploy(vm *object.VirtualMachine) error { 159 if err := cmd.InjectOvfEnv(vm); err != nil { 160 return err 161 } 162 163 if err := cmd.PowerOn(vm); err != nil { 164 return err 165 } 166 167 if err := cmd.WaitForIP(vm); err != nil { 168 return err 169 } 170 171 return nil 172} 173 174func (cmd *ovfx) Map(op []Property) (p []types.KeyValue) { 175 for _, v := range op { 176 p = append(p, v.KeyValue) 177 } 178 179 return 180} 181 182func (cmd *ovfx) NetworkMap(e *ovf.Envelope) (p []types.OvfNetworkMapping) { 183 ctx := context.TODO() 184 finder, err := cmd.DatastoreFlag.Finder() 185 if err != nil { 186 return 187 } 188 189 networks := map[string]string{} 190 191 if e.Network != nil { 192 for _, net := range e.Network.Networks { 193 networks[net.Name] = net.Name 194 } 195 } 196 197 for _, net := range cmd.Options.NetworkMapping { 198 networks[net.Name] = net.Network 199 } 200 201 for src, dst := range networks { 202 if net, err := finder.Network(ctx, dst); err == nil { 203 p = append(p, types.OvfNetworkMapping{ 204 Name: src, 205 Network: net.Reference(), 206 }) 207 } 208 } 209 return 210} 211 212func (cmd *ovfx) Import(fpath string) (*types.ManagedObjectReference, error) { 213 ctx := context.TODO() 214 o, err := cmd.ReadOvf(fpath) 215 if err != nil { 216 return nil, err 217 } 218 219 e, err := cmd.ReadEnvelope(o) 220 if err != nil { 221 return nil, fmt.Errorf("failed to parse ovf: %s", err.Error()) 222 } 223 224 name := "Govc Virtual Appliance" 225 if e.VirtualSystem != nil { 226 name = e.VirtualSystem.ID 227 if e.VirtualSystem.Name != nil { 228 name = *e.VirtualSystem.Name 229 } 230 } 231 232 // Override name from options if specified 233 if cmd.Options.Name != nil { 234 name = *cmd.Options.Name 235 } 236 237 // Override name from arguments if specified 238 if cmd.Name != "" { 239 name = cmd.Name 240 } 241 242 cisp := types.OvfCreateImportSpecParams{ 243 DiskProvisioning: cmd.Options.DiskProvisioning, 244 EntityName: name, 245 IpAllocationPolicy: cmd.Options.IPAllocationPolicy, 246 IpProtocol: cmd.Options.IPProtocol, 247 OvfManagerCommonParams: types.OvfManagerCommonParams{ 248 DeploymentOption: cmd.Options.Deployment, 249 Locale: "US"}, 250 PropertyMapping: cmd.Map(cmd.Options.PropertyMapping), 251 NetworkMapping: cmd.NetworkMap(e), 252 } 253 254 host, err := cmd.HostSystemIfSpecified() 255 if err != nil { 256 return nil, err 257 } 258 259 if cmd.ResourcePool == nil { 260 if host == nil { 261 cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool() 262 } else { 263 cmd.ResourcePool, err = host.ResourcePool(ctx) 264 } 265 if err != nil { 266 return nil, err 267 } 268 } 269 270 m := ovf.NewManager(cmd.Client) 271 spec, err := m.CreateImportSpec(ctx, string(o), cmd.ResourcePool, cmd.Datastore, cisp) 272 if err != nil { 273 return nil, err 274 } 275 if spec.Error != nil { 276 return nil, errors.New(spec.Error[0].LocalizedMessage) 277 } 278 if spec.Warning != nil { 279 for _, w := range spec.Warning { 280 _, _ = cmd.Log(fmt.Sprintf("Warning: %s\n", w.LocalizedMessage)) 281 } 282 } 283 284 if cmd.Options.Annotation != "" { 285 switch s := spec.ImportSpec.(type) { 286 case *types.VirtualMachineImportSpec: 287 s.ConfigSpec.Annotation = cmd.Options.Annotation 288 case *types.VirtualAppImportSpec: 289 s.VAppConfigSpec.Annotation = cmd.Options.Annotation 290 } 291 } 292 293 folder, err := cmd.FolderOrDefault("vm") 294 if err != nil { 295 return nil, err 296 } 297 298 lease, err := cmd.ResourcePool.ImportVApp(ctx, spec.ImportSpec, folder, host) 299 if err != nil { 300 return nil, err 301 } 302 303 info, err := lease.Wait(ctx, spec.FileItem) 304 if err != nil { 305 return nil, err 306 } 307 308 u := lease.StartUpdater(ctx, info) 309 defer u.Done() 310 311 for _, i := range info.Items { 312 err = cmd.Upload(ctx, lease, i) 313 if err != nil { 314 return nil, err 315 } 316 } 317 318 return &info.Entity, lease.Complete(ctx) 319} 320 321func (cmd *ovfx) Upload(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error { 322 file := item.Path 323 324 f, size, err := cmd.Open(file) 325 if err != nil { 326 return err 327 } 328 defer f.Close() 329 330 logger := cmd.ProgressLogger(fmt.Sprintf("Uploading %s... ", path.Base(file))) 331 defer logger.Wait() 332 333 opts := soap.Upload{ 334 ContentLength: size, 335 Progress: logger, 336 } 337 338 return lease.Upload(ctx, item, f, opts) 339} 340 341func (cmd *ovfx) PowerOn(vm *object.VirtualMachine) error { 342 ctx := context.TODO() 343 if !cmd.Options.PowerOn { 344 return nil 345 } 346 347 cmd.Log("Powering on VM...\n") 348 349 task, err := vm.PowerOn(ctx) 350 if err != nil { 351 return err 352 } 353 354 if _, err = task.WaitForResult(ctx, nil); err != nil { 355 return err 356 } 357 358 return nil 359} 360 361func (cmd *ovfx) InjectOvfEnv(vm *object.VirtualMachine) error { 362 if !cmd.Options.InjectOvfEnv { 363 return nil 364 } 365 366 cmd.Log("Injecting OVF environment...\n") 367 368 var opts []types.BaseOptionValue 369 370 a := cmd.Client.ServiceContent.About 371 372 // build up Environment in order to marshal to xml 373 var props []ovf.EnvProperty 374 for _, p := range cmd.Options.PropertyMapping { 375 props = append(props, ovf.EnvProperty{ 376 Key: p.Key, 377 Value: p.Value, 378 }) 379 } 380 381 env := ovf.Env{ 382 EsxID: vm.Reference().Value, 383 Platform: &ovf.PlatformSection{ 384 Kind: a.Name, 385 Version: a.Version, 386 Vendor: a.Vendor, 387 Locale: "US", 388 }, 389 Property: &ovf.PropertySection{ 390 Properties: props, 391 }, 392 } 393 394 opts = append(opts, &types.OptionValue{ 395 Key: "guestinfo.ovfEnv", 396 Value: env.MarshalManual(), 397 }) 398 399 ctx := context.Background() 400 401 task, err := vm.Reconfigure(ctx, types.VirtualMachineConfigSpec{ 402 ExtraConfig: opts, 403 }) 404 405 if err != nil { 406 return err 407 } 408 409 return task.Wait(ctx) 410} 411 412func (cmd *ovfx) WaitForIP(vm *object.VirtualMachine) error { 413 ctx := context.TODO() 414 if !cmd.Options.PowerOn || !cmd.Options.WaitForIP { 415 return nil 416 } 417 418 cmd.Log("Waiting for IP address...\n") 419 ip, err := vm.WaitForIP(ctx) 420 if err != nil { 421 return err 422 } 423 424 cmd.Log(fmt.Sprintf("Received IP address: %s\n", ip)) 425 return nil 426} 427