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