github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/post-processor/vagrant/virtualbox.go (about)

     1  package vagrant
     2  
     3  import (
     4  	"archive/tar"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/mitchellh/packer/common"
     8  	"github.com/mitchellh/packer/packer"
     9  	"io"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"regexp"
    15  )
    16  
    17  type VBoxBoxConfig struct {
    18  	common.PackerConfig `mapstructure:",squash"`
    19  
    20  	OutputPath          string `mapstructure:"output"`
    21  	VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
    22  
    23  	tpl *packer.ConfigTemplate
    24  }
    25  
    26  type VBoxVagrantfileTemplate struct {
    27  	BaseMacAddress string
    28  }
    29  
    30  type VBoxBoxPostProcessor struct {
    31  	config VBoxBoxConfig
    32  }
    33  
    34  func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
    35  	md, err := common.DecodeConfig(&p.config, raws...)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	p.config.tpl, err = packer.NewConfigTemplate()
    41  	if err != nil {
    42  		return err
    43  	}
    44  	p.config.tpl.UserVars = p.config.PackerUserVars
    45  
    46  	// Accumulate any errors
    47  	errs := common.CheckUnusedConfig(md)
    48  
    49  	validates := map[string]*string{
    50  		"output":               &p.config.OutputPath,
    51  		"vagrantfile_template": &p.config.VagrantfileTemplate,
    52  	}
    53  
    54  	for n, ptr := range validates {
    55  		if err := p.config.tpl.Validate(*ptr); err != nil {
    56  			errs = packer.MultiErrorAppend(
    57  				errs, fmt.Errorf("Error parsing %s: %s", n, err))
    58  		}
    59  	}
    60  
    61  	if errs != nil && len(errs.Errors) > 0 {
    62  		return errs
    63  	}
    64  
    65  	return nil
    66  }
    67  
    68  func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
    69  	var err error
    70  
    71  	// Compile the output path
    72  	outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
    73  		ArtifactId: artifact.Id(),
    74  		BuildName:  p.config.PackerBuildName,
    75  		Provider:   "virtualbox",
    76  	})
    77  	if err != nil {
    78  		return nil, false, err
    79  	}
    80  
    81  	// Create a temporary directory for us to build the contents of the box in
    82  	dir, err := ioutil.TempDir("", "packer")
    83  	if err != nil {
    84  		return nil, false, err
    85  	}
    86  	defer os.RemoveAll(dir)
    87  
    88  	// Copy all of the original contents into the temporary directory
    89  	for _, path := range artifact.Files() {
    90  
    91  		// We treat OVA files specially, we unpack those into the temporary
    92  		// directory so we can get the resulting disk and OVF.
    93  		if extension := filepath.Ext(path); extension == ".ova" {
    94  			ui.Message(fmt.Sprintf("Unpacking OVA: %s", path))
    95  			if err := DecompressOva(dir, filepath.Base(path)); err != nil {
    96  				return nil, false, err
    97  			}
    98  		} else {
    99  			ui.Message(fmt.Sprintf("Copying: %s", path))
   100  			dstPath := filepath.Join(dir, filepath.Base(path))
   101  			if err := CopyContents(dstPath, path); err != nil {
   102  				return nil, false, err
   103  			}
   104  		}
   105  
   106  	}
   107  
   108  	// Create the Vagrantfile from the template
   109  	tplData := &VBoxVagrantfileTemplate{}
   110  	tplData.BaseMacAddress, err = p.findBaseMacAddress(dir)
   111  	if err != nil {
   112  		return nil, false, err
   113  	}
   114  
   115  	vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
   116  	if err != nil {
   117  		return nil, false, err
   118  	}
   119  	defer vf.Close()
   120  
   121  	vagrantfileContents := defaultVBoxVagrantfile
   122  	if p.config.VagrantfileTemplate != "" {
   123  		f, err := os.Open(p.config.VagrantfileTemplate)
   124  		if err != nil {
   125  			return nil, false, err
   126  		}
   127  		defer f.Close()
   128  
   129  		contents, err := ioutil.ReadAll(f)
   130  		if err != nil {
   131  			return nil, false, err
   132  		}
   133  
   134  		vagrantfileContents = string(contents)
   135  	}
   136  
   137  	vagrantfileContents, err = p.config.tpl.Process(vagrantfileContents, tplData)
   138  	if err != nil {
   139  		return nil, false, fmt.Errorf("Error writing Vagrantfile: %s", err)
   140  	}
   141  	vf.Write([]byte(vagrantfileContents))
   142  	vf.Close()
   143  
   144  	// Create the metadata
   145  	metadata := map[string]string{"provider": "virtualbox"}
   146  	if err := WriteMetadata(dir, metadata); err != nil {
   147  		return nil, false, err
   148  	}
   149  
   150  	// Rename the OVF file to box.ovf, as required by Vagrant
   151  	ui.Message("Renaming the OVF to box.ovf...")
   152  	if err := p.renameOVF(dir); err != nil {
   153  		return nil, false, err
   154  	}
   155  
   156  	// Compress the directory to the given output path
   157  	ui.Message(fmt.Sprintf("Compressing box..."))
   158  	if err := DirToBox(outputPath, dir, ui); err != nil {
   159  		return nil, false, err
   160  	}
   161  
   162  	return NewArtifact("virtualbox", outputPath), false, nil
   163  }
   164  
   165  func (p *VBoxBoxPostProcessor) findOvf(dir string) (string, error) {
   166  	log.Println("Looking for OVF in artifact...")
   167  	file_matches, err := filepath.Glob(filepath.Join(dir, "*.ovf"))
   168  	if err != nil {
   169  		return "", err
   170  	}
   171  
   172  	if len(file_matches) > 1 {
   173  		return "", errors.New("More than one OVF file in VirtualBox artifact.")
   174  	}
   175  
   176  	if len(file_matches) < 1 {
   177  		return "", errors.New("ovf file couldn't be found")
   178  	}
   179  
   180  	return file_matches[0], err
   181  }
   182  
   183  func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
   184  	log.Println("Looking for OVF to rename...")
   185  	ovf, err := p.findOvf(dir)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	log.Printf("Renaming: '%s' => box.ovf", ovf)
   191  	return os.Rename(ovf, filepath.Join(dir, "box.ovf"))
   192  }
   193  
   194  func (p *VBoxBoxPostProcessor) findBaseMacAddress(dir string) (string, error) {
   195  	log.Println("Looking for OVF for base mac address...")
   196  	ovf, err := p.findOvf(dir)
   197  	if err != nil {
   198  		return "", err
   199  	}
   200  
   201  	f, err := os.Open(ovf)
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  	defer f.Close()
   206  
   207  	data, err := ioutil.ReadAll(f)
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  
   212  	re := regexp.MustCompile(`<Adapter slot="0".+?MACAddress="(.+?)"`)
   213  	matches := re.FindSubmatch(data)
   214  	if matches == nil {
   215  		return "", errors.New("can't find base mac address in OVF")
   216  	}
   217  
   218  	log.Printf("Base mac address: %s", string(matches[1]))
   219  	return string(matches[1]), nil
   220  }
   221  
   222  // DecompressOva takes an ova file and decompresses it into the target
   223  // directory.
   224  func DecompressOva(dir, src string) error {
   225  	log.Printf("Turning ova to dir: %s => %s", src, dir)
   226  	srcF, err := os.Open(src)
   227  	if err != nil {
   228  		return err
   229  	}
   230  	defer srcF.Close()
   231  
   232  	tarReader := tar.NewReader(srcF)
   233  	for {
   234  		hdr, err := tarReader.Next()
   235  		if hdr == nil || err == io.EOF {
   236  			break
   237  		}
   238  
   239  		info := hdr.FileInfo()
   240  
   241  		// Shouldn't be any directories, skip them
   242  		if info.IsDir() {
   243  			continue
   244  		}
   245  
   246  		// We wrap this in an anonymous function so that the defers
   247  		// inside are handled more quickly so we can give up file handles.
   248  		err = func() error {
   249  			path := filepath.Join(dir, info.Name())
   250  			output, err := os.Create(path)
   251  			if err != nil {
   252  				return err
   253  			}
   254  			defer output.Close()
   255  
   256  			os.Chmod(path, info.Mode())
   257  			os.Chtimes(path, hdr.AccessTime, hdr.ModTime)
   258  			_, err = io.Copy(output, tarReader)
   259  			return err
   260  		}()
   261  		if err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  var defaultVBoxVagrantfile = `
   270  Vagrant.configure("2") do |config|
   271  config.vm.base_mac = "{{ .BaseMacAddress }}"
   272  end
   273  `