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