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