Accept content library path in iso_paths (#9801)
This commit is contained in:
parent
fbfe31ceaf
commit
2152fa3313
|
@ -2,8 +2,12 @@ package driver
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
@ -57,6 +61,21 @@ func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
|
|||
}, 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) {
|
||||
var p []string
|
||||
if len(params) == 0 {
|
||||
|
@ -85,6 +104,42 @@ func (ds *Datastore) ResolvePath(path string) string {
|
|||
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 {
|
||||
p := soap.DefaultUpload
|
||||
ctx := ds.driver.ctx
|
||||
|
@ -128,3 +183,28 @@ func RemoveDatastorePrefix(path string) string {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ type Driver struct {
|
|||
// context that controls the authenticated sessions used to run the VM commands
|
||||
ctx context.Context
|
||||
client *govmomi.Client
|
||||
vimClient *vim25.Client
|
||||
restClient *RestClient
|
||||
finder *find.Finder
|
||||
datacenter *object.Datacenter
|
||||
|
@ -67,8 +68,9 @@ func NewDriver(config *ConnectConfig) (*Driver, error) {
|
|||
finder.SetDatacenter(datacenter)
|
||||
|
||||
d := Driver{
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
vimClient: vimClient,
|
||||
restClient: &RestClient{
|
||||
client: rest.NewClient(vimClient),
|
||||
credentials: credentials,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/vapi/library"
|
||||
)
|
||||
|
||||
|
@ -9,7 +14,7 @@ type Library struct {
|
|||
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)
|
||||
l, err := lm.GetLibraryByName(d.ctx, name)
|
||||
if err != nil {
|
||||
|
@ -32,5 +37,77 @@ func (d *Driver) FindContentLibraryItem(libraryId string, name string) (*library
|
|||
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]
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -689,7 +689,7 @@ func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
|
|||
return err
|
||||
}
|
||||
|
||||
l, err := vm.driver.FindContentLibrary(ovf.Target.LibraryID)
|
||||
l, err := vm.driver.FindContentLibraryByName(ovf.Target.LibraryID)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item != nil {
|
||||
if err == nil {
|
||||
// Updates existing library item
|
||||
ovf.Target.LibraryItemID = item.ID
|
||||
}
|
||||
|
@ -726,7 +723,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro
|
|||
return err
|
||||
}
|
||||
|
||||
l, err := vm.driver.FindContentLibrary(template.Library)
|
||||
l, err := vm.driver.FindContentLibraryByName(template.Library)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -970,11 +967,21 @@ func (vm *VirtualMachine) AddCdrom(controllerType string, isoPath string) error
|
|||
return err
|
||||
}
|
||||
|
||||
if isoPath != "" {
|
||||
devices.InsertIso(cdrom, isoPath)
|
||||
if datastoreIsoPath != "" {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,14 @@ import (
|
|||
type CDRomConfig struct {
|
||||
// Which controller to use. Example: `sata`. Defaults to `ide`.
|
||||
CdromType string `mapstructure:"cdrom_type"`
|
||||
// List of datastore paths to ISO files that will be mounted to the VM.
|
||||
// Example: `"[datastore1] ISO/ubuntu.iso"`.
|
||||
// List of Datastore or Content Library paths to ISO files that will be mounted to the VM.
|
||||
// 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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,11 @@
|
|||
|
||||
- `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.
|
||||
Example: `"[datastore1] ISO/ubuntu.iso"`.
|
||||
- `iso_paths` ([]string) - List of Datastore or Content Library paths to ISO files that will be mounted to the VM.
|
||||
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"
|
||||
]
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue