github.com/cloudfoundry-incubator/stembuild@v0.0.0-20211223202937-5b61d62226c6/package_stemcell/packagers/vmdk_packager.go (about) 1 package packagers 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "crypto/sha1" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "os/signal" 15 "path/filepath" 16 "time" 17 18 "github.com/cloudfoundry-incubator/stembuild/filesystem" 19 20 "github.com/cloudfoundry-incubator/stembuild/package_stemcell/ovftool" 21 "github.com/cloudfoundry-incubator/stembuild/package_stemcell/package_parameters" 22 "github.com/cloudfoundry-incubator/stembuild/templates" 23 ) 24 25 const Gigabyte = 1024 * 1024 * 1024 26 27 type VmdkPackager struct { 28 Image string 29 Stemcell string 30 Manifest string 31 Sha1sum string 32 tmpdir string 33 Stop chan struct{} 34 Debugf func(format string, a ...interface{}) 35 BuildOptions package_parameters.VmdkPackageParameters 36 } 37 38 type CancelReadSeeker struct { 39 rs io.ReadSeeker 40 stop chan struct{} 41 } 42 43 var ErrInterupt = errors.New("interrupt") 44 45 func (r *CancelReadSeeker) Seek(offset int64, whence int) (int64, error) { 46 select { 47 case <-r.stop: 48 return 0, ErrInterupt 49 default: 50 return r.rs.Seek(offset, whence) 51 } 52 } 53 54 func (r *CancelReadSeeker) Read(p []byte) (int, error) { 55 select { 56 case <-r.stop: 57 return 0, ErrInterupt 58 default: 59 return r.rs.Read(p) 60 } 61 } 62 63 type CancelWriter struct { 64 w io.Writer 65 stop chan struct{} 66 } 67 68 func (w *CancelWriter) Write(p []byte) (int, error) { 69 select { 70 case <-w.stop: 71 return 0, ErrInterupt 72 default: 73 return w.w.Write(p) 74 } 75 } 76 77 type CancelReader struct { 78 r io.Reader 79 stop chan struct{} 80 } 81 82 func (r *CancelReader) Read(p []byte) (int, error) { 83 select { 84 case <-r.stop: 85 return 0, ErrInterupt 86 default: 87 return r.r.Read(p) 88 } 89 } 90 91 // returns a io.Writer that returns an error when VmdkPackager c is stopped 92 func (c *VmdkPackager) Writer(w io.Writer) *CancelWriter { 93 return &CancelWriter{w: w, stop: c.Stop} 94 } 95 96 // returns a io.Reader that returns an error when VmdkPackager c is stopped 97 func (c *VmdkPackager) Reader(r io.Reader) *CancelReader { 98 return &CancelReader{r: r, stop: c.Stop} 99 } 100 101 func (c *VmdkPackager) StopConfig() { 102 c.Debugf("stopping config") 103 defer c.Cleanup() // make sure this runs! 104 close(c.Stop) 105 } 106 107 func (c *VmdkPackager) Cleanup() { 108 if c.tmpdir == "" { 109 return 110 } 111 // check if directory exists to make Cleanup idempotent 112 if _, err := os.Stat(c.tmpdir); err == nil { 113 c.Debugf("deleting temp directory: %s", c.tmpdir) 114 os.RemoveAll(c.tmpdir) 115 } 116 } 117 118 func (c *VmdkPackager) AddTarFile(tr *tar.Writer, name string) error { 119 c.Debugf("adding file (%s) to tar archive", name) 120 f, err := os.Open(name) 121 if err != nil { 122 return err 123 } 124 defer f.Close() 125 fi, err := f.Stat() 126 if err != nil { 127 return err 128 } 129 hdr, err := tar.FileInfoHeader(fi, "") 130 if err != nil { 131 return err 132 } 133 if err := tr.WriteHeader(hdr); err != nil { 134 return err 135 } 136 if _, err := io.Copy(tr, c.Reader(f)); err != nil { 137 return err 138 } 139 return nil 140 } 141 142 func (c *VmdkPackager) TempDir() (string, error) { 143 if c.tmpdir != "" { 144 if _, err := os.Stat(c.tmpdir); err != nil { 145 c.Debugf("unable to stat temp dir (%s) was it deleted?", c.tmpdir) 146 return "", fmt.Errorf("opening temp directory: %s", c.tmpdir) 147 } 148 return c.tmpdir, nil 149 } 150 name, err := ioutil.TempDir(c.BuildOptions.OutputDir, "stemcell-") 151 if err != nil { 152 return "", fmt.Errorf("creating temp directory: %s", err) 153 } 154 c.tmpdir = name 155 c.Debugf("created temp directory: %s", name) 156 return c.tmpdir, nil 157 } 158 159 func (c *VmdkPackager) CreateStemcell() error { 160 c.Debugf("creating stemcell") 161 162 // programming errors - panic! 163 if c.Manifest == "" { 164 panic("CreateStemcell: empty manifest") 165 } 166 if c.Image == "" { 167 panic("CreateStemcell: empty image") 168 } 169 170 tmpdir, err := c.TempDir() 171 if err != nil { 172 return err 173 } 174 175 c.Stemcell = filepath.Join(tmpdir, StemcellFilename(c.BuildOptions.Version, c.BuildOptions.OSVersion)) 176 stemcell, err := os.OpenFile(c.Stemcell, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) 177 if err != nil { 178 return err 179 } 180 defer stemcell.Close() 181 c.Debugf("created temp stemcell: %s", c.Stemcell) 182 183 errorf := func(format string, a ...interface{}) error { 184 stemcell.Close() 185 os.Remove(c.Stemcell) 186 return fmt.Errorf(format, a...) 187 } 188 189 t := time.Now() 190 w := gzip.NewWriter(c.Writer(stemcell)) 191 tr := tar.NewWriter(w) 192 193 c.Debugf("adding image file to stemcell tarball: %s", c.Image) 194 if err := c.AddTarFile(tr, c.Image); err != nil { 195 return errorf("creating stemcell: %s", err) 196 } 197 198 c.Debugf("adding manifest file to stemcell tarball: %s", c.Manifest) 199 if err := c.AddTarFile(tr, c.Manifest); err != nil { 200 return errorf("creating stemcell: %s", err) 201 } 202 203 if err := tr.Close(); err != nil { 204 return errorf("creating stemcell: %s", err) 205 } 206 207 if err := w.Close(); err != nil { 208 return errorf("creating stemcell: %s", err) 209 } 210 211 c.Debugf("created stemcell in: %s", time.Since(t)) 212 213 return nil 214 } 215 216 func (c *VmdkPackager) ConvertVMX2OVA(vmx, ova string) error { 217 const errFmt = "converting vmx to ova: %s\n" + 218 "-- BEGIN STDERR OUTPUT -- :\n%s\n-- END STDERR OUTPUT --\n" 219 220 searchPaths, err := ovftool.SearchPaths() 221 if err != nil { 222 return err 223 } 224 ovfpath, err := ovftool.Ovftool(searchPaths) 225 if err != nil { 226 return err 227 } 228 229 // ignore stdout 230 var stderr bytes.Buffer 231 232 cmd := exec.Command(ovfpath, vmx, ova) 233 cmd.Stderr = &stderr 234 if err := cmd.Start(); err != nil { 235 return fmt.Errorf("ovftool: %s", err) 236 } 237 c.Debugf("converting vmx to ova with cmd: %s %s", cmd.Path, cmd.Args[1:]) 238 239 // Wait for process exit or interupt 240 errCh := make(chan error, 1) 241 go func() { errCh <- cmd.Wait() }() 242 243 select { 244 case <-c.Stop: 245 if cmd.Process != nil { 246 c.Debugf("received stop signall killing ovftool process") 247 cmd.Process.Kill() 248 } 249 return ErrInterupt 250 case err := <-errCh: 251 if err != nil { 252 return fmt.Errorf(errFmt, err, stderr.String()) 253 } 254 } 255 256 return nil 257 } 258 259 // CreateImage, converts a vmdk to a gzip compressed image file and records the 260 // sha1 sum of the resulting image. 261 func (c *VmdkPackager) CreateImage() error { 262 c.Debugf("Creating [image] from [vmdk]: %s", c.BuildOptions.VMDKFile) 263 264 tmpdir, err := c.TempDir() 265 if err != nil { 266 return err 267 } 268 269 var hwVersion int 270 switch c.BuildOptions.OSVersion { 271 case "2012R2": 272 hwVersion = 9 273 case "2016", "1803", "2019": 274 hwVersion = 10 275 } 276 277 vmxPath := filepath.Join(tmpdir, "image.vmx") 278 vmdkPath, err := filepath.Abs(c.BuildOptions.VMDKFile) 279 if err != nil { 280 return err 281 } 282 if err := templates.WriteVMXTemplate(vmdkPath, hwVersion, vmxPath); err != nil { 283 return err 284 } 285 286 ovaPath := filepath.Join(tmpdir, "image.ova") 287 if err := c.ConvertVMX2OVA(vmxPath, ovaPath); err != nil { 288 return err 289 } 290 291 // reader 292 r, err := os.Open(ovaPath) 293 if err != nil { 294 return err 295 } 296 defer r.Close() 297 298 // image file (writer) 299 c.Image = filepath.Join(tmpdir, "image") 300 f, err := os.OpenFile(c.Image, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) 301 if err != nil { 302 return err 303 } 304 defer f.Close() 305 306 // calculate sha1 while writing image file 307 h := sha1.New() 308 w := gzip.NewWriter(io.MultiWriter(f, h)) 309 310 if _, err := io.Copy(w, r); err != nil { 311 return err 312 } 313 if err := w.Close(); err != nil { 314 return err 315 } 316 317 c.Sha1sum = fmt.Sprintf("%x", h.Sum(nil)) 318 c.Debugf("Sha1 of image (%s): %s", c.Image, c.Sha1sum) 319 return nil 320 } 321 322 func (c *VmdkPackager) ConvertVMDK() (string, error) { 323 if err := c.CreateImage(); err != nil { 324 return "", err 325 } 326 _, err := c.TempDir() 327 328 if err != nil { 329 return "", err 330 } 331 manifest := CreateManifest(c.BuildOptions.OSVersion, c.BuildOptions.Version, c.Sha1sum) 332 if err := WriteManifest(manifest, c.tmpdir); err != nil { 333 return "", err 334 } 335 c.Manifest = filepath.Join(c.tmpdir, "stemcell.MF") 336 337 if err := c.CreateStemcell(); err != nil { 338 return "", err 339 } 340 341 stemcellPath := filepath.Join(c.BuildOptions.OutputDir, filepath.Base(c.Stemcell)) 342 c.Debugf("moving stemcell (%s) to: %s", c.Stemcell, stemcellPath) 343 344 if err := os.Rename(c.Stemcell, stemcellPath); err != nil { 345 return "", err 346 } 347 return stemcellPath, nil 348 } 349 350 func (c *VmdkPackager) catchInterruptSignal() { 351 ch := make(chan os.Signal, 64) 352 signal.Notify(ch, os.Interrupt) 353 stopping := false 354 for sig := range ch { 355 c.Debugf("received signal: %s", sig) 356 if stopping { 357 fmt.Fprintf(os.Stderr, "received second (%s) signal - exiting now\n", sig) 358 c.Cleanup() // remove temp dir 359 os.Exit(1) 360 } 361 stopping = true 362 fmt.Fprintf(os.Stderr, "received (%s) signal cleaning up\n", sig) 363 c.StopConfig() 364 } 365 } 366 367 func (c VmdkPackager) Package() error { 368 369 go c.catchInterruptSignal() 370 371 start := time.Now() 372 373 stemcellPath, err := c.ConvertVMDK() 374 if err != nil { 375 fmt.Fprintf(os.Stderr, "Error: %s\n", err) 376 c.Cleanup() // remove temp dir 377 return err 378 } 379 380 c.Debugf("created stemcell (%s) in: %s", stemcellPath, time.Since(start)) 381 fmt.Printf("created stemcell: %s", stemcellPath) 382 383 c.Cleanup() 384 return nil 385 } 386 387 func (c VmdkPackager) ValidateSourceParameters() error { 388 if validVMDK, err := IsValidVMDK(c.BuildOptions.VMDKFile); err != nil { 389 return err 390 } else if !validVMDK { 391 return errors.New("invalid VMDK file") 392 } 393 394 searchPaths, err := ovftool.SearchPaths() 395 if err != nil { 396 return fmt.Errorf("could not get search paths for Ovftool: %s", err) 397 } 398 _, err = ovftool.Ovftool(searchPaths) 399 if err != nil { 400 return fmt.Errorf("could not locate Ovftool on PATH: %s", err) 401 } 402 return nil 403 } 404 405 func IsValidVMDK(vmdk string) (bool, error) { 406 fi, err := os.Stat(vmdk) 407 if err != nil { 408 return false, err 409 } 410 if !fi.Mode().IsRegular() { 411 return false, nil 412 } 413 return true, nil 414 } 415 416 func (p VmdkPackager) ValidateFreeSpaceForPackage(fs filesystem.FileSystem) error { 417 fi, err := os.Stat(p.BuildOptions.VMDKFile) 418 if err != nil { 419 errorMsg := fmt.Sprintf("could not get vmdk info: %s", err) 420 return errors.New(errorMsg) 421 } 422 vmdkSize := fi.Size() 423 424 // make sure there is enough space for ova + stemcell and some leftover 425 // ova and stemcell will be the size of the vmdk in the worst case scenario 426 427 minSpace := uint64(vmdkSize)*2 + (Gigabyte / 2) 428 429 enoughSpace, requiredSpace, err := hasAtLeastFreeDiskSpace(minSpace, fs, filepath.Dir(p.BuildOptions.VMDKFile)) 430 if err != nil { 431 errorMsg := fmt.Sprintf("could not check free space on disk: %s", err) 432 return errors.New(errorMsg) 433 } 434 435 if !enoughSpace { 436 errorMsg := fmt.Sprintf("Not enough space to create stemcell. Free up %d MB and try again", requiredSpace/(1024*1024)) 437 return errors.New(errorMsg) 438 439 } 440 return nil 441 442 } 443 444 func hasAtLeastFreeDiskSpace(minFreeSpace uint64, fs filesystem.FileSystem, path string) (bool, uint64, error) { 445 446 freeSpace, err := fs.GetAvailableDiskSpace(path) 447 448 if err != nil { 449 return false, 0, err 450 } 451 return freeSpace >= minFreeSpace, minFreeSpace - freeSpace, nil 452 }