Accept content library path in iso_paths (#9801)
This commit is contained in:
parent
fbfe31ceaf
commit
2152fa3313
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
// 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,
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in New Issue