Accept content library path in iso_paths (#9801)

This commit is contained in:
Sylvia Moss 2020-08-24 16:54:30 +02:00 committed by GitHub
parent fbfe31ceaf
commit 2152fa3313
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 312 additions and 18 deletions

View File

@ -2,8 +2,12 @@ package driver
import ( import (
"fmt" "fmt"
"path"
"regexp"
"strings"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
@ -57,6 +61,21 @@ func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
}, nil }, nil
} }
func (d *Driver) GetDatastoreName(id string) (string, error) {
obj := types.ManagedObjectReference{
Type: "Datastore",
Value: id,
}
pc := property.DefaultCollector(d.vimClient)
var me mo.ManagedEntity
err := pc.RetrieveOne(d.ctx, obj, []string{"name"}, &me)
if err != nil {
return id, err
}
return me.Name, nil
}
func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) { func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
var p []string var p []string
if len(params) == 0 { if len(params) == 0 {
@ -85,6 +104,42 @@ func (ds *Datastore) ResolvePath(path string) string {
return ds.ds.Path(path) return ds.ds.Path(path)
} }
// The file ID isn't available via the API, so we use DatastoreBrowser to search
func (d *Driver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
ref := types.ManagedObjectReference{Type: "Datastore", Value: datastoreID}
ds := object.NewDatastore(d.vimClient, ref)
b, err := ds.Browser(d.ctx)
if err != nil {
return filename, err
}
ext := path.Ext(filename)
pat := strings.Replace(filename, ext, "*"+ext, 1)
spec := types.HostDatastoreBrowserSearchSpec{
MatchPattern: []string{pat},
}
task, err := b.SearchDatastore(d.ctx, dir, &spec)
if err != nil {
return filename, err
}
info, err := task.WaitForResult(d.ctx, nil)
if err != nil {
return filename, err
}
res, ok := info.Result.(types.HostDatastoreBrowserSearchResults)
if !ok {
return filename, fmt.Errorf("search(%s) result type=%T", pat, info.Result)
}
if len(res.File) != 1 {
return filename, fmt.Errorf("search(%s) result files=%d", pat, len(res.File))
}
return res.File[0].GetFileInfo().Path, nil
}
func (ds *Datastore) UploadFile(src, dst, host string, set_host_for_datastore_uploads bool) error { func (ds *Datastore) UploadFile(src, dst, host string, set_host_for_datastore_uploads bool) error {
p := soap.DefaultUpload p := soap.DefaultUpload
ctx := ds.driver.ctx ctx := ds.driver.ctx
@ -128,3 +183,28 @@ func RemoveDatastorePrefix(path string) string {
return path return path
} }
} }
type DatastoreIsoPath struct {
path string
}
func (d *DatastoreIsoPath) Validate() bool {
// Matches:
// [datastore] /dir/subdir/file
// [datastore] dir/subdir/file
// [] /dir/subdir/file
// /dir/subdir/file or dir/subdir/file
matched, _ := regexp.MatchString(`^((\[\w*\])?\s*([^\[\]]+))$`, d.path)
return matched
}
func (d *DatastoreIsoPath) GetFilePath() string {
filePath := d.path
parts := strings.Split(d.path, "]")
if len(parts) > 1 {
// removes datastore name from path
filePath = parts[1]
filePath = strings.TrimLeft(filePath, " ")
}
return filePath
}

View File

@ -0,0 +1,54 @@
package driver
import "testing"
func TestDatastoreIsoPath(t *testing.T) {
tc := []struct {
isoPath string
filePath string
valid bool
}{
{
isoPath: "[datastore] dir/subdir/file",
filePath: "dir/subdir/file",
valid: true,
},
{
isoPath: "[] dir/subdir/file",
filePath: "dir/subdir/file",
valid: true,
},
{
isoPath: "dir/subdir/file",
filePath: "dir/subdir/file",
valid: true,
},
{
isoPath: "[datastore] /dir/subdir/file",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "/dir/subdir/file [datastore] ",
valid: false,
},
{
isoPath: "[datastore][] /dir/subdir/file",
valid: false,
},
}
for _, c := range tc {
dsIsoPath := &DatastoreIsoPath{path: c.isoPath}
if dsIsoPath.Validate() != c.valid {
t.Fatalf("Expecting %s to be %t but was %t", c.isoPath, c.valid, !c.valid)
}
if !c.valid {
continue
}
filePath := dsIsoPath.GetFilePath()
if filePath != c.filePath {
t.Fatalf("Expecting %s but got %s", c.filePath, filePath)
}
}
}

View File

@ -19,6 +19,7 @@ type Driver struct {
// context that controls the authenticated sessions used to run the VM commands // context that controls the authenticated sessions used to run the VM commands
ctx context.Context ctx context.Context
client *govmomi.Client client *govmomi.Client
vimClient *vim25.Client
restClient *RestClient restClient *RestClient
finder *find.Finder finder *find.Finder
datacenter *object.Datacenter datacenter *object.Datacenter
@ -67,8 +68,9 @@ func NewDriver(config *ConnectConfig) (*Driver, error) {
finder.SetDatacenter(datacenter) finder.SetDatacenter(datacenter)
d := Driver{ d := Driver{
ctx: ctx, ctx: ctx,
client: client, client: client,
vimClient: vimClient,
restClient: &RestClient{ restClient: &RestClient{
client: rest.NewClient(vimClient), client: rest.NewClient(vimClient),
credentials: credentials, credentials: credentials,

View File

@ -1,6 +1,11 @@
package driver package driver
import ( import (
"fmt"
"log"
"path"
"strings"
"github.com/vmware/govmomi/vapi/library" "github.com/vmware/govmomi/vapi/library"
) )
@ -9,7 +14,7 @@ type Library struct {
library *library.Library library *library.Library
} }
func (d *Driver) FindContentLibrary(name string) (*Library, error) { func (d *Driver) FindContentLibraryByName(name string) (*Library, error) {
lm := library.NewManager(d.restClient.client) lm := library.NewManager(d.restClient.client)
l, err := lm.GetLibraryByName(d.ctx, name) l, err := lm.GetLibraryByName(d.ctx, name)
if err != nil { if err != nil {
@ -32,5 +37,77 @@ func (d *Driver) FindContentLibraryItem(libraryId string, name string) (*library
return &item, nil return &item, nil
} }
} }
return nil, nil return nil, fmt.Errorf("Item %s not found", name)
}
func (d *Driver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
log.Printf("Check if ISO path is a Content Library path")
err := d.restClient.Login(d.ctx)
if err != nil {
log.Printf("vCenter client not available. ISO path not identified as a Content Library path")
return isoPath, err
}
libraryFilePath := &LibraryFilePath{path: isoPath}
err = libraryFilePath.Validate()
if err != nil {
log.Printf("ISO path not identified as a Content Library path")
return isoPath, err
}
libraryName := libraryFilePath.GetLibraryName()
itemName := libraryFilePath.GetLibraryItemName()
isoFile := libraryFilePath.GetFileName()
lib, err := d.FindContentLibraryByName(libraryName)
if err != nil {
log.Printf("ISO path not identified as a Content Library path")
return isoPath, err
}
log.Printf("ISO path identified as a Content Library path")
log.Printf("Finding the equivalent datastore path for the Content Library ISO file path")
libItem, err := d.FindContentLibraryItem(lib.library.ID, itemName)
if err != nil {
log.Printf("[WARN] Couldn't find item %s: %s", itemName, err.Error())
return isoPath, err
}
datastoreName, err := d.GetDatastoreName(lib.library.Storage[0].DatastoreID)
if err != nil {
log.Printf("[WARN] Couldn't find datastore name for library %s", libraryName)
return isoPath, err
}
libItemDir := fmt.Sprintf("[%s] contentlib-%s/%s", datastoreName, lib.library.ID, libItem.ID)
isoFilePath, err := d.GetDatastoreFilePath(lib.library.Storage[0].DatastoreID, libItemDir, isoFile)
if err != nil {
log.Printf("[WARN] Couldn't find datastore ID path for %s", isoFile)
return isoPath, err
}
_ = d.restClient.Logout(d.ctx)
return path.Join(libItemDir, isoFilePath), nil
}
type LibraryFilePath struct {
path string
}
func (l *LibraryFilePath) Validate() error {
l.path = strings.TrimLeft(l.path, "/")
parts := strings.Split(l.path, "/")
if len(parts) != 3 {
return fmt.Errorf("Not a valid Content Library File path. The path must contain the nanmes for the library, item and file.")
}
return nil
}
func (l *LibraryFilePath) GetLibraryName() string {
return strings.Split(l.path, "/")[0]
}
func (l *LibraryFilePath) GetLibraryItemName() string {
return strings.Split(l.path, "/")[1]
}
func (l *LibraryFilePath) GetFileName() string {
return strings.Split(l.path, "/")[2]
} }

View File

@ -0,0 +1,62 @@
package driver
import "testing"
func TestLibraryFilePath(t *testing.T) {
tc := []struct {
filePath string
libraryName string
libraryItemName string
fileName string
valid bool
}{
{
filePath: "lib/item/file",
libraryName: "lib",
libraryItemName: "item",
fileName: "file",
valid: true,
},
{
filePath: "/lib/item/file",
libraryName: "lib",
libraryItemName: "item",
fileName: "file",
valid: true,
},
{
filePath: "/lib/item/filedir/file",
valid: false,
},
{
filePath: "/lib/item",
valid: false,
},
{
filePath: "/lib",
valid: false,
},
}
for _, c := range tc {
libraryFilePath := &LibraryFilePath{path: c.filePath}
if err := libraryFilePath.Validate(); err != nil {
if c.valid {
t.Fatalf("Expecting %s to be valid", c.filePath)
}
continue
}
libraryName := libraryFilePath.GetLibraryName()
if libraryName != c.libraryName {
t.Fatalf("Expecting %s but got %s", c.libraryName, libraryName)
}
libraryItemName := libraryFilePath.GetLibraryItemName()
if libraryItemName != c.libraryItemName {
t.Fatalf("Expecting %s but got %s", c.libraryItemName, libraryItemName)
}
fileName := libraryFilePath.GetFileName()
if fileName != c.fileName {
t.Fatalf("Expecting %s but got %s", c.fileName, fileName)
}
}
}

View File

@ -689,7 +689,7 @@ func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
return err return err
} }
l, err := vm.driver.FindContentLibrary(ovf.Target.LibraryID) l, err := vm.driver.FindContentLibraryByName(ovf.Target.LibraryID)
if err != nil { if err != nil {
return err return err
} }
@ -699,10 +699,7 @@ func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
} }
item, err := vm.driver.FindContentLibraryItem(l.library.ID, ovf.Spec.Name) item, err := vm.driver.FindContentLibraryItem(l.library.ID, ovf.Spec.Name)
if err != nil { if err == nil {
return err
}
if item != nil {
// Updates existing library item // Updates existing library item
ovf.Target.LibraryItemID = item.ID ovf.Target.LibraryItemID = item.ID
} }
@ -726,7 +723,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro
return err return err
} }
l, err := vm.driver.FindContentLibrary(template.Library) l, err := vm.driver.FindContentLibraryByName(template.Library)
if err != nil { if err != nil {
return err return err
} }
@ -944,7 +941,7 @@ func newVGPUProfile(vGPUProfile string) types.VirtualPCIPassthrough {
} }
} }
func (vm *VirtualMachine) AddCdrom(controllerType string, isoPath string) error { func (vm *VirtualMachine) AddCdrom(controllerType string, datastoreIsoPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx) devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil { if err != nil {
return err return err
@ -970,11 +967,21 @@ func (vm *VirtualMachine) AddCdrom(controllerType string, isoPath string) error
return err return err
} }
if isoPath != "" { if datastoreIsoPath != "" {
devices.InsertIso(cdrom, isoPath) ds := &DatastoreIsoPath{path: datastoreIsoPath}
if !ds.Validate() {
return fmt.Errorf("%s is not a valid iso path", datastoreIsoPath)
}
if libPath, err := vm.driver.FindContentLibraryFileDatastorePath(ds.GetFilePath()); err == nil {
datastoreIsoPath = libPath
} else {
log.Printf("Using %s as the datastore path", datastoreIsoPath)
}
devices.InsertIso(cdrom, datastoreIsoPath)
} }
log.Printf("Creating CD-ROM on controller '%v' with iso '%v'", controller, isoPath) log.Printf("Creating CD-ROM on controller '%v' with iso '%v'", controller, datastoreIsoPath)
return vm.addDevice(cdrom) return vm.addDevice(cdrom)
} }

View File

@ -15,8 +15,14 @@ import (
type CDRomConfig struct { type CDRomConfig struct {
// Which controller to use. Example: `sata`. Defaults to `ide`. // Which controller to use. Example: `sata`. Defaults to `ide`.
CdromType string `mapstructure:"cdrom_type"` CdromType string `mapstructure:"cdrom_type"`
// List of datastore paths to ISO files that will be mounted to the VM. // List of Datastore or Content Library paths to ISO files that will be mounted to the VM.
// Example: `"[datastore1] ISO/ubuntu.iso"`. // Here's an HCL2 example:
// ```hcl
// iso_paths = [
// "[datastore1] ISO/ubuntu.iso",
// "Packer Library Test/ubuntu-16.04.6-server-amd64/ubuntu-16.04.6-server-amd64.iso"
// ]
// ```
ISOPaths []string `mapstructure:"iso_paths"` ISOPaths []string `mapstructure:"iso_paths"`
} }

View File

@ -2,5 +2,11 @@
- `cdrom_type` (string) - Which controller to use. Example: `sata`. Defaults to `ide`. - `cdrom_type` (string) - Which controller to use. Example: `sata`. Defaults to `ide`.
- `iso_paths` ([]string) - List of datastore paths to ISO files that will be mounted to the VM. - `iso_paths` ([]string) - List of Datastore or Content Library paths to ISO files that will be mounted to the VM.
Example: `"[datastore1] ISO/ubuntu.iso"`. Here's an HCL2 example:
```hcl
iso_paths = [
"[datastore1] ISO/ubuntu.iso",
"Packer Library Test/ubuntu-16.04.6-server-amd64/ubuntu-16.04.6-server-amd64.iso"
]
```