github.com/vmware/govmomi@v0.51.0/guest/file_manager.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package guest
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"net"
    11  	"net/url"
    12  	"os"
    13  	"sync"
    14  
    15  	"github.com/vmware/govmomi/internal"
    16  	"github.com/vmware/govmomi/property"
    17  	"github.com/vmware/govmomi/vim25"
    18  	"github.com/vmware/govmomi/vim25/methods"
    19  	"github.com/vmware/govmomi/vim25/mo"
    20  	"github.com/vmware/govmomi/vim25/types"
    21  )
    22  
    23  type FileManager struct {
    24  	types.ManagedObjectReference
    25  
    26  	vm types.ManagedObjectReference
    27  
    28  	c *vim25.Client
    29  
    30  	mu    *sync.Mutex
    31  	hosts map[string]string
    32  }
    33  
    34  func (m FileManager) Reference() types.ManagedObjectReference {
    35  	return m.ManagedObjectReference
    36  }
    37  
    38  func (m FileManager) ChangeFileAttributes(ctx context.Context, auth types.BaseGuestAuthentication, guestFilePath string, fileAttributes types.BaseGuestFileAttributes) error {
    39  	req := types.ChangeFileAttributesInGuest{
    40  		This:           m.Reference(),
    41  		Vm:             m.vm,
    42  		Auth:           auth,
    43  		GuestFilePath:  guestFilePath,
    44  		FileAttributes: fileAttributes,
    45  	}
    46  
    47  	_, err := methods.ChangeFileAttributesInGuest(ctx, m.c, &req)
    48  	return err
    49  }
    50  
    51  func (m FileManager) CreateTemporaryDirectory(ctx context.Context, auth types.BaseGuestAuthentication, prefix, suffix string, path string) (string, error) {
    52  	req := types.CreateTemporaryDirectoryInGuest{
    53  		This:          m.Reference(),
    54  		Vm:            m.vm,
    55  		Auth:          auth,
    56  		Prefix:        prefix,
    57  		Suffix:        suffix,
    58  		DirectoryPath: path,
    59  	}
    60  
    61  	res, err := methods.CreateTemporaryDirectoryInGuest(ctx, m.c, &req)
    62  	if err != nil {
    63  		return "", err
    64  	}
    65  
    66  	return res.Returnval, nil
    67  }
    68  
    69  func (m FileManager) CreateTemporaryFile(ctx context.Context, auth types.BaseGuestAuthentication, prefix, suffix string, path string) (string, error) {
    70  	req := types.CreateTemporaryFileInGuest{
    71  		This:          m.Reference(),
    72  		Vm:            m.vm,
    73  		Auth:          auth,
    74  		Prefix:        prefix,
    75  		Suffix:        suffix,
    76  		DirectoryPath: path,
    77  	}
    78  
    79  	res, err := methods.CreateTemporaryFileInGuest(ctx, m.c, &req)
    80  	if err != nil {
    81  		return "", err
    82  	}
    83  
    84  	return res.Returnval, nil
    85  }
    86  
    87  func (m FileManager) DeleteDirectory(ctx context.Context, auth types.BaseGuestAuthentication, directoryPath string, recursive bool) error {
    88  	req := types.DeleteDirectoryInGuest{
    89  		This:          m.Reference(),
    90  		Vm:            m.vm,
    91  		Auth:          auth,
    92  		DirectoryPath: directoryPath,
    93  		Recursive:     recursive,
    94  	}
    95  
    96  	_, err := methods.DeleteDirectoryInGuest(ctx, m.c, &req)
    97  	return err
    98  }
    99  
   100  func (m FileManager) DeleteFile(ctx context.Context, auth types.BaseGuestAuthentication, filePath string) error {
   101  	req := types.DeleteFileInGuest{
   102  		This:     m.Reference(),
   103  		Vm:       m.vm,
   104  		Auth:     auth,
   105  		FilePath: filePath,
   106  	}
   107  
   108  	_, err := methods.DeleteFileInGuest(ctx, m.c, &req)
   109  	return err
   110  }
   111  
   112  // escape hatch to disable the preference to use ESX host management IP for guest file transfer
   113  var useGuestTransferIP = os.Getenv("GOVMOMI_USE_GUEST_TRANSFER_IP") != "false"
   114  
   115  // TransferURL rewrites the url with a valid hostname and adds the host's thumbprint.
   116  // The InitiateFileTransfer{From,To}Guest methods return a URL with the host set to "*" when connected directly to ESX,
   117  // but return the address of VM's runtime host when connected to vCenter.
   118  func (m FileManager) TransferURL(ctx context.Context, u string) (*url.URL, error) {
   119  	turl, err := url.Parse(u)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	if turl.Hostname() == "*" {
   125  		turl.Host = m.c.URL().Host // Also use Client's port, to support port forwarding
   126  	}
   127  
   128  	if !m.c.IsVC() {
   129  		return turl, nil // we already connected to the ESX host and have its thumbprint
   130  	}
   131  
   132  	name := turl.Hostname()
   133  	port := turl.Port()
   134  	isHostname := net.ParseIP(name) == nil
   135  
   136  	m.mu.Lock()
   137  	mname, ok := m.hosts[name]
   138  	m.mu.Unlock()
   139  
   140  	if ok {
   141  		turl.Host = mname
   142  		return turl, nil
   143  	} else {
   144  		mname = turl.Host
   145  	}
   146  
   147  	c := property.DefaultCollector(m.c)
   148  
   149  	var vm mo.VirtualMachine
   150  	err = c.RetrieveOne(ctx, m.vm, []string{"name", "runtime.host"}, &vm)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	if vm.Runtime.Host == nil {
   156  		return turl, nil // won't matter if the VM was powered off since the call to InitiateFileTransfer will fail
   157  	}
   158  
   159  	// VC supports the use of a Unix domain socket for guest file transfers.
   160  	if internal.UsingEnvoySidecar(m.c) {
   161  		// Rewrite the URL in the format unix://
   162  		// Reciever must use a custom dialer.
   163  		// Nil check performed above, so Host is safe to access.
   164  		return internal.HostGatewayTransferURL(turl, *vm.Runtime.Host), nil
   165  	}
   166  
   167  	// Determine host thumbprint, address etc. to be able to trust host.
   168  	props := []string{
   169  		"name",
   170  		"runtime.connectionState",
   171  		"summary.config.sslThumbprint",
   172  	}
   173  
   174  	if isHostname {
   175  		props = append(props, "config.virtualNicManagerInfo.netConfig")
   176  	}
   177  
   178  	var host mo.HostSystem
   179  	err = c.RetrieveOne(ctx, *vm.Runtime.Host, props, &host)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	if isHostname {
   185  		if host.Config == nil {
   186  			return nil, fmt.Errorf("guest TransferURL failed for vm %q (%s): host %q (%s) config==nil, connectionState==%s",
   187  				vm.Name, vm.Self,
   188  				host.Name, host.Self, host.Runtime.ConnectionState)
   189  		}
   190  
   191  		// InitiateFileTransfer{To,From}Guest methods return an ESX host's inventory name (HostSystem.Name).
   192  		// This name was used to add the host to vCenter and cannot be changed (unless the host is removed from inventory and added back with another name).
   193  		// The name used when adding to VC may not resolvable by this client's DNS, so we prefer an ESX management IP.
   194  		// However, if there is more than one management vNIC, we don't know which IP(s) the client has a route to.
   195  		// Leave the hostname as-is in that case or if the env var has disabled the preference.
   196  		ips := internal.HostSystemManagementIPs(host.Config.VirtualNicManagerInfo.NetConfig)
   197  		if len(ips) == 1 && useGuestTransferIP {
   198  			mname = net.JoinHostPort(ips[0].String(), port)
   199  
   200  			turl.Host = mname
   201  		}
   202  	}
   203  
   204  	m.mu.Lock()
   205  	m.hosts[name] = mname
   206  	m.mu.Unlock()
   207  
   208  	m.c.SetThumbprint(turl.Host, host.Summary.Config.SslThumbprint)
   209  
   210  	return turl, nil
   211  }
   212  
   213  func (m FileManager) InitiateFileTransferFromGuest(ctx context.Context, auth types.BaseGuestAuthentication, guestFilePath string) (*types.FileTransferInformation, error) {
   214  	req := types.InitiateFileTransferFromGuest{
   215  		This:          m.Reference(),
   216  		Vm:            m.vm,
   217  		Auth:          auth,
   218  		GuestFilePath: guestFilePath,
   219  	}
   220  
   221  	res, err := methods.InitiateFileTransferFromGuest(ctx, m.c, &req)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	return &res.Returnval, nil
   227  }
   228  
   229  func (m FileManager) InitiateFileTransferToGuest(ctx context.Context, auth types.BaseGuestAuthentication, guestFilePath string, fileAttributes types.BaseGuestFileAttributes, fileSize int64, overwrite bool) (string, error) {
   230  	req := types.InitiateFileTransferToGuest{
   231  		This:           m.Reference(),
   232  		Vm:             m.vm,
   233  		Auth:           auth,
   234  		GuestFilePath:  guestFilePath,
   235  		FileAttributes: fileAttributes,
   236  		FileSize:       fileSize,
   237  		Overwrite:      overwrite,
   238  	}
   239  
   240  	res, err := methods.InitiateFileTransferToGuest(ctx, m.c, &req)
   241  	if err != nil {
   242  		return "", err
   243  	}
   244  
   245  	return res.Returnval, nil
   246  }
   247  
   248  func (m FileManager) ListFiles(ctx context.Context, auth types.BaseGuestAuthentication, filePath string, index int32, maxResults int32, matchPattern string) (*types.GuestListFileInfo, error) {
   249  	req := types.ListFilesInGuest{
   250  		This:         m.Reference(),
   251  		Vm:           m.vm,
   252  		Auth:         auth,
   253  		FilePath:     filePath,
   254  		Index:        index,
   255  		MaxResults:   maxResults,
   256  		MatchPattern: matchPattern,
   257  	}
   258  
   259  	res, err := methods.ListFilesInGuest(ctx, m.c, &req)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	return &res.Returnval, nil
   265  }
   266  
   267  func (m FileManager) MakeDirectory(ctx context.Context, auth types.BaseGuestAuthentication, directoryPath string, createParentDirectories bool) error {
   268  	req := types.MakeDirectoryInGuest{
   269  		This:                    m.Reference(),
   270  		Vm:                      m.vm,
   271  		Auth:                    auth,
   272  		DirectoryPath:           directoryPath,
   273  		CreateParentDirectories: createParentDirectories,
   274  	}
   275  
   276  	_, err := methods.MakeDirectoryInGuest(ctx, m.c, &req)
   277  	return err
   278  }
   279  
   280  func (m FileManager) MoveDirectory(ctx context.Context, auth types.BaseGuestAuthentication, srcDirectoryPath string, dstDirectoryPath string) error {
   281  	req := types.MoveDirectoryInGuest{
   282  		This:             m.Reference(),
   283  		Vm:               m.vm,
   284  		Auth:             auth,
   285  		SrcDirectoryPath: srcDirectoryPath,
   286  		DstDirectoryPath: dstDirectoryPath,
   287  	}
   288  
   289  	_, err := methods.MoveDirectoryInGuest(ctx, m.c, &req)
   290  	return err
   291  }
   292  
   293  func (m FileManager) MoveFile(ctx context.Context, auth types.BaseGuestAuthentication, srcFilePath string, dstFilePath string, overwrite bool) error {
   294  	req := types.MoveFileInGuest{
   295  		This:        m.Reference(),
   296  		Vm:          m.vm,
   297  		Auth:        auth,
   298  		SrcFilePath: srcFilePath,
   299  		DstFilePath: dstFilePath,
   300  		Overwrite:   overwrite,
   301  	}
   302  
   303  	_, err := methods.MoveFileInGuest(ctx, m.c, &req)
   304  	return err
   305  }