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