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 `