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  }