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