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