github.com/vmware/govmomi@v0.51.0/object/datastore_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 object
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"path"
    15  	"strings"
    16  
    17  	"github.com/vmware/govmomi/vim25/progress"
    18  	"github.com/vmware/govmomi/vim25/soap"
    19  )
    20  
    21  // DatastoreFileManager combines FileManager and VirtualDiskManager to manage files on a Datastore
    22  type DatastoreFileManager struct {
    23  	Datacenter         *Datacenter
    24  	Datastore          *Datastore
    25  	FileManager        *FileManager
    26  	VirtualDiskManager *VirtualDiskManager
    27  
    28  	Force            bool
    29  	DatacenterTarget *Datacenter
    30  }
    31  
    32  // NewFileManager creates a new instance of DatastoreFileManager
    33  func (d Datastore) NewFileManager(dc *Datacenter, force bool) *DatastoreFileManager {
    34  	c := d.Client()
    35  
    36  	m := &DatastoreFileManager{
    37  		Datacenter:         dc,
    38  		Datastore:          &d,
    39  		FileManager:        NewFileManager(c),
    40  		VirtualDiskManager: NewVirtualDiskManager(c),
    41  		Force:              force,
    42  		DatacenterTarget:   dc,
    43  	}
    44  
    45  	return m
    46  }
    47  
    48  func (m *DatastoreFileManager) WithProgress(ctx context.Context, s progress.Sinker) context.Context {
    49  	return context.WithValue(ctx, m, s)
    50  }
    51  
    52  func (m *DatastoreFileManager) wait(ctx context.Context, task *Task) error {
    53  	var logger progress.Sinker
    54  	if s, ok := ctx.Value(m).(progress.Sinker); ok {
    55  		logger = s
    56  	}
    57  	_, err := task.WaitForResult(ctx, logger)
    58  	return err
    59  }
    60  
    61  // Delete dispatches to the appropriate Delete method based on file name extension
    62  func (m *DatastoreFileManager) Delete(ctx context.Context, name string) error {
    63  	switch path.Ext(name) {
    64  	case ".vmdk":
    65  		return m.DeleteVirtualDisk(ctx, name)
    66  	default:
    67  		return m.DeleteFile(ctx, name)
    68  	}
    69  }
    70  
    71  // DeleteFile calls FileManager.DeleteDatastoreFile
    72  func (m *DatastoreFileManager) DeleteFile(ctx context.Context, name string) error {
    73  	p := m.Path(name)
    74  
    75  	task, err := m.FileManager.DeleteDatastoreFile(ctx, p.String(), m.Datacenter)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	return m.wait(ctx, task)
    81  }
    82  
    83  // DeleteVirtualDisk calls VirtualDiskManager.DeleteVirtualDisk
    84  // Regardless of the Datastore type, DeleteVirtualDisk will fail if 'ddb.deletable=false',
    85  // so if Force=true this method attempts to set 'ddb.deletable=true' before starting the delete task.
    86  func (m *DatastoreFileManager) DeleteVirtualDisk(ctx context.Context, name string) error {
    87  	p := m.Path(name)
    88  
    89  	var merr error
    90  
    91  	if m.Force {
    92  		merr = m.markDiskAsDeletable(ctx, p)
    93  	}
    94  
    95  	task, err := m.VirtualDiskManager.DeleteVirtualDisk(ctx, p.String(), m.Datacenter)
    96  	if err != nil {
    97  		log.Printf("markDiskAsDeletable(%s): %s", p, merr)
    98  		return err
    99  	}
   100  
   101  	return m.wait(ctx, task)
   102  }
   103  
   104  // CopyFile calls FileManager.CopyDatastoreFile
   105  func (m *DatastoreFileManager) CopyFile(ctx context.Context, src string, dst string) error {
   106  	srcp := m.Path(src)
   107  	dstp := m.Path(dst)
   108  
   109  	task, err := m.FileManager.CopyDatastoreFile(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	return m.wait(ctx, task)
   115  }
   116  
   117  // Copy dispatches to the appropriate FileManager or VirtualDiskManager Copy method based on file name extension
   118  func (m *DatastoreFileManager) Copy(ctx context.Context, src string, dst string) error {
   119  	srcp := m.Path(src)
   120  	dstp := m.Path(dst)
   121  
   122  	f := m.FileManager.CopyDatastoreFile
   123  
   124  	if srcp.IsVMDK() {
   125  		// types.VirtualDiskSpec=nil as it is not implemented by vCenter
   126  		f = func(ctx context.Context, src string, srcDC *Datacenter, dst string, dstDC *Datacenter, force bool) (*Task, error) {
   127  			return m.VirtualDiskManager.CopyVirtualDisk(ctx, src, srcDC, dst, dstDC, nil, force)
   128  		}
   129  	}
   130  
   131  	task, err := f(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	return m.wait(ctx, task)
   137  }
   138  
   139  // MoveFile calls FileManager.MoveDatastoreFile
   140  func (m *DatastoreFileManager) MoveFile(ctx context.Context, src string, dst string) error {
   141  	srcp := m.Path(src)
   142  	dstp := m.Path(dst)
   143  
   144  	task, err := m.FileManager.MoveDatastoreFile(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	return m.wait(ctx, task)
   150  }
   151  
   152  // Move dispatches to the appropriate FileManager or VirtualDiskManager Move method based on file name extension
   153  func (m *DatastoreFileManager) Move(ctx context.Context, src string, dst string) error {
   154  	srcp := m.Path(src)
   155  	dstp := m.Path(dst)
   156  
   157  	f := m.FileManager.MoveDatastoreFile
   158  
   159  	if srcp.IsVMDK() {
   160  		f = m.VirtualDiskManager.MoveVirtualDisk
   161  	}
   162  
   163  	task, err := f(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	return m.wait(ctx, task)
   169  }
   170  
   171  // Path converts path name to a DatastorePath
   172  func (m *DatastoreFileManager) Path(name string) *DatastorePath {
   173  	var p DatastorePath
   174  
   175  	if !p.FromString(name) {
   176  		p.Path = name
   177  		p.Datastore = m.Datastore.Name()
   178  	}
   179  
   180  	return &p
   181  }
   182  
   183  func (m *DatastoreFileManager) markDiskAsDeletable(ctx context.Context, path *DatastorePath) error {
   184  	r, _, err := m.Datastore.Download(ctx, path.Path, &soap.DefaultDownload)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	defer r.Close()
   190  
   191  	hasFlag := false
   192  	buf := new(bytes.Buffer)
   193  
   194  	s := bufio.NewScanner(&io.LimitedReader{R: r, N: 2048}) // should be only a few hundred bytes, limit to be sure
   195  
   196  	for s.Scan() {
   197  		line := s.Text()
   198  		if strings.HasPrefix(line, "ddb.deletable") {
   199  			hasFlag = true
   200  			continue
   201  		}
   202  
   203  		fmt.Fprintln(buf, line)
   204  	}
   205  
   206  	if err := s.Err(); err != nil {
   207  		return err // any error other than EOF
   208  	}
   209  
   210  	if !hasFlag {
   211  		return nil // already deletable, so leave as-is
   212  	}
   213  
   214  	// rewrite the .vmdk with ddb.deletable flag removed (the default is true)
   215  	return m.Datastore.Upload(ctx, buf, path.Path, &soap.DefaultUpload)
   216  }