github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/post-processor/vagrant/post-processor.go (about) 1 // vagrant implements the packer.PostProcessor interface and adds a 2 // post-processor that turns artifacts of known builders into Vagrant 3 // boxes. 4 package vagrant 5 6 import ( 7 "compress/flate" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "text/template" 13 14 "github.com/mitchellh/packer/common" 15 "github.com/mitchellh/packer/packer" 16 ) 17 18 var builtins = map[string]string{ 19 "mitchellh.amazonebs": "aws", 20 "mitchellh.amazon.instance": "aws", 21 "mitchellh.virtualbox": "virtualbox", 22 "mitchellh.vmware": "vmware", 23 "pearkes.digitalocean": "digitalocean", 24 "packer.parallels": "parallels", 25 "MSOpenTech.hyperv": "hyperv", 26 "transcend.qemu": "libvirt", 27 } 28 29 type Config struct { 30 common.PackerConfig `mapstructure:",squash"` 31 32 CompressionLevel int `mapstructure:"compression_level"` 33 Include []string `mapstructure:"include"` 34 OutputPath string `mapstructure:"output"` 35 Override map[string]interface{} 36 VagrantfileTemplate string `mapstructure:"vagrantfile_template"` 37 38 tpl *packer.ConfigTemplate 39 } 40 41 type PostProcessor struct { 42 configs map[string]*Config 43 } 44 45 func (p *PostProcessor) Configure(raws ...interface{}) error { 46 p.configs = make(map[string]*Config) 47 p.configs[""] = new(Config) 48 if err := p.configureSingle(p.configs[""], raws...); err != nil { 49 return err 50 } 51 52 // Go over any of the provider-specific overrides and load those up. 53 for name, override := range p.configs[""].Override { 54 subRaws := make([]interface{}, len(raws)+1) 55 copy(subRaws, raws) 56 subRaws[len(raws)] = override 57 58 config := new(Config) 59 p.configs[name] = config 60 if err := p.configureSingle(config, subRaws...); err != nil { 61 return fmt.Errorf("Error configuring %s: %s", name, err) 62 } 63 } 64 65 return nil 66 } 67 68 func (p *PostProcessor) PostProcessProvider(name string, provider Provider, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 69 config := p.configs[""] 70 if specificConfig, ok := p.configs[name]; ok { 71 config = specificConfig 72 } 73 74 ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", name)) 75 76 outputPath, err := config.tpl.Process(config.OutputPath, &outputPathTemplate{ 77 ArtifactId: artifact.Id(), 78 BuildName: config.PackerBuildName, 79 Provider: name, 80 }) 81 if err != nil { 82 return nil, false, err 83 } 84 85 // Create a temporary directory for us to build the contents of the box in 86 dir, err := ioutil.TempDir("", "packer") 87 if err != nil { 88 return nil, false, err 89 } 90 defer os.RemoveAll(dir) 91 92 // Copy all of the includes files into the temporary directory 93 for _, src := range config.Include { 94 ui.Message(fmt.Sprintf("Copying from include: %s", src)) 95 dst := filepath.Join(dir, filepath.Base(src)) 96 if err := CopyContents(dst, src); err != nil { 97 err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err) 98 return nil, false, err 99 } 100 } 101 102 // Run the provider processing step 103 vagrantfile, metadata, err := provider.Process(ui, artifact, dir) 104 if err != nil { 105 return nil, false, err 106 } 107 108 // Write the metadata we got 109 if err := WriteMetadata(dir, metadata); err != nil { 110 return nil, false, err 111 } 112 113 // Write our Vagrantfile 114 var customVagrantfile string 115 if config.VagrantfileTemplate != "" { 116 ui.Message(fmt.Sprintf("Using custom Vagrantfile: %s", config.VagrantfileTemplate)) 117 customBytes, err := ioutil.ReadFile(config.VagrantfileTemplate) 118 if err != nil { 119 return nil, false, err 120 } 121 122 customVagrantfile = string(customBytes) 123 } 124 125 f, err := os.Create(filepath.Join(dir, "Vagrantfile")) 126 if err != nil { 127 return nil, false, err 128 } 129 130 t := template.Must(template.New("root").Parse(boxVagrantfileContents)) 131 err = t.Execute(f, &vagrantfileTemplate{ 132 ProviderVagrantfile: vagrantfile, 133 CustomVagrantfile: customVagrantfile, 134 }) 135 f.Close() 136 if err != nil { 137 return nil, false, err 138 } 139 140 // Create the box 141 if err := DirToBox(outputPath, dir, ui, config.CompressionLevel); err != nil { 142 return nil, false, err 143 } 144 145 return NewArtifact(name, outputPath), provider.KeepInputArtifact(), nil 146 } 147 148 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 149 150 name, ok := builtins[artifact.BuilderId()] 151 if !ok { 152 return nil, false, fmt.Errorf( 153 "Unknown artifact type, can't build box: %s", artifact.BuilderId()) 154 } 155 156 provider := providerForName(name) 157 if provider == nil { 158 // This shouldn't happen since we hard code all of these ourselves 159 panic(fmt.Sprintf("bad provider name: %s", name)) 160 } 161 162 return p.PostProcessProvider(name, provider, ui, artifact) 163 } 164 165 func (p *PostProcessor) configureSingle(config *Config, raws ...interface{}) error { 166 md, err := common.DecodeConfig(config, raws...) 167 if err != nil { 168 return err 169 } 170 171 config.tpl, err = packer.NewConfigTemplate() 172 if err != nil { 173 return err 174 } 175 config.tpl.UserVars = config.PackerUserVars 176 177 // Defaults 178 if config.OutputPath == "" { 179 config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box" 180 } 181 182 found := false 183 for _, k := range md.Keys { 184 if k == "compression_level" { 185 found = true 186 break 187 } 188 } 189 190 if !found { 191 config.CompressionLevel = flate.DefaultCompression 192 } 193 194 // Accumulate any errors 195 errs := common.CheckUnusedConfig(md) 196 197 templates := map[string]*string{ 198 "vagrantfile_template": &config.VagrantfileTemplate, 199 } 200 201 for key, ptr := range templates { 202 *ptr, err = config.tpl.Process(*ptr, nil) 203 if err != nil { 204 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", key, err)) 205 } 206 } 207 208 validates := map[string]*string{ 209 "output": &config.OutputPath, 210 "vagrantfile_template": &config.VagrantfileTemplate, 211 } 212 213 if config.VagrantfileTemplate != "" { 214 _, err := os.Stat(config.VagrantfileTemplate) 215 if err != nil { 216 errs = packer.MultiErrorAppend(errs, fmt.Errorf("vagrantfile_template '%s' does not exist", config.VagrantfileTemplate)) 217 } 218 } 219 220 for n, ptr := range validates { 221 if err := config.tpl.Validate(*ptr); err != nil { 222 errs = packer.MultiErrorAppend( 223 errs, fmt.Errorf("Error parsing %s: %s", n, err)) 224 } 225 } 226 227 if errs != nil && len(errs.Errors) > 0 { 228 return errs 229 } 230 231 return nil 232 } 233 234 func providerForName(name string) Provider { 235 switch name { 236 case "aws": 237 return new(AWSProvider) 238 case "digitalocean": 239 return new(DigitalOceanProvider) 240 case "virtualbox": 241 return new(VBoxProvider) 242 case "vmware": 243 return new(VMwareProvider) 244 case "parallels": 245 return new(ParallelsProvider) 246 case "hyperv": 247 return new(HypervProvider) 248 case "libvirt": 249 return new(LibVirtProvider) 250 default: 251 return nil 252 } 253 } 254 255 // OutputPathTemplate is the structure that is availalable within the 256 // OutputPath variables. 257 type outputPathTemplate struct { 258 ArtifactId string 259 BuildName string 260 Provider string 261 } 262 263 type vagrantfileTemplate struct { 264 ProviderVagrantfile string 265 CustomVagrantfile string 266 } 267 268 const boxVagrantfileContents string = ` 269 # The contents below were provided by the Packer Vagrant post-processor 270 {{ .ProviderVagrantfile }} 271 272 # The contents below (if any) are custom contents provided by the 273 # Packer template during image build. 274 {{ .CustomVagrantfile }} 275 `