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