github.com/aclaygray/packer@v1.3.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/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 "Azure.ResourceManagement.VMImage": "azure", 35 "packer.post-processor.docker-import": "docker", 36 "packer.post-processor.docker-tag": "docker", 37 "packer.post-processor.docker-push": "docker", 38 } 39 40 type Config struct { 41 common.PackerConfig `mapstructure:",squash"` 42 43 CompressionLevel int `mapstructure:"compression_level"` 44 Include []string `mapstructure:"include"` 45 OutputPath string `mapstructure:"output"` 46 Override map[string]interface{} 47 VagrantfileTemplate string `mapstructure:"vagrantfile_template"` 48 49 ctx interpolate.Context 50 } 51 52 type PostProcessor struct { 53 configs map[string]*Config 54 } 55 56 func (p *PostProcessor) Configure(raws ...interface{}) error { 57 p.configs = make(map[string]*Config) 58 p.configs[""] = new(Config) 59 if err := p.configureSingle(p.configs[""], raws...); err != nil { 60 return err 61 } 62 63 // Go over any of the provider-specific overrides and load those up. 64 for name, override := range p.configs[""].Override { 65 subRaws := make([]interface{}, len(raws)+1) 66 copy(subRaws, raws) 67 subRaws[len(raws)] = override 68 69 config := new(Config) 70 p.configs[name] = config 71 if err := p.configureSingle(config, subRaws...); err != nil { 72 return fmt.Errorf("Error configuring %s: %s", name, err) 73 } 74 } 75 76 return nil 77 } 78 79 func (p *PostProcessor) PostProcessProvider(name string, provider Provider, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 80 config := p.configs[""] 81 if specificConfig, ok := p.configs[name]; ok { 82 config = specificConfig 83 } 84 85 ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", name)) 86 87 config.ctx.Data = &outputPathTemplate{ 88 ArtifactId: artifact.Id(), 89 BuildName: config.PackerBuildName, 90 Provider: name, 91 } 92 outputPath, err := interpolate.Render(config.OutputPath, &config.ctx) 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("Using custom Vagrantfile: %s", config.VagrantfileTemplate)) 129 customBytes, err := ioutil.ReadFile(config.VagrantfileTemplate) 130 if err != nil { 131 return nil, false, err 132 } 133 134 customVagrantfile = string(customBytes) 135 } 136 137 f, err := os.Create(filepath.Join(dir, "Vagrantfile")) 138 if err != nil { 139 return nil, false, err 140 } 141 142 t := template.Must(template.New("root").Parse(boxVagrantfileContents)) 143 err = t.Execute(f, &vagrantfileTemplate{ 144 ProviderVagrantfile: vagrantfile, 145 CustomVagrantfile: customVagrantfile, 146 }) 147 f.Close() 148 if err != nil { 149 return nil, false, err 150 } 151 152 // Create the box 153 if err := DirToBox(outputPath, dir, ui, config.CompressionLevel); err != nil { 154 return nil, false, err 155 } 156 157 return NewArtifact(name, outputPath), provider.KeepInputArtifact(), nil 158 } 159 160 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 161 162 name, ok := builtins[artifact.BuilderId()] 163 if !ok { 164 return nil, false, fmt.Errorf( 165 "Unknown artifact type, can't build box: %s", artifact.BuilderId()) 166 } 167 168 provider := providerForName(name) 169 if provider == nil { 170 // This shouldn't happen since we hard code all of these ourselves 171 panic(fmt.Sprintf("bad provider name: %s", name)) 172 } 173 174 return p.PostProcessProvider(name, provider, ui, artifact) 175 } 176 177 func (p *PostProcessor) configureSingle(c *Config, raws ...interface{}) error { 178 var md mapstructure.Metadata 179 err := config.Decode(c, &config.DecodeOpts{ 180 Metadata: &md, 181 Interpolate: true, 182 InterpolateContext: &c.ctx, 183 InterpolateFilter: &interpolate.RenderFilter{ 184 Exclude: []string{ 185 "output", 186 }, 187 }, 188 }, raws...) 189 if err != nil { 190 return err 191 } 192 193 // Defaults 194 if c.OutputPath == "" { 195 c.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box" 196 } 197 198 found := false 199 for _, k := range md.Keys { 200 if k == "compression_level" { 201 found = true 202 break 203 } 204 } 205 206 if !found { 207 c.CompressionLevel = flate.DefaultCompression 208 } 209 210 var errs *packer.MultiError 211 if c.VagrantfileTemplate != "" { 212 _, err := os.Stat(c.VagrantfileTemplate) 213 if err != nil { 214 errs = packer.MultiErrorAppend(errs, fmt.Errorf( 215 "vagrantfile_template '%s' does not exist", c.VagrantfileTemplate)) 216 } 217 } 218 219 if errs != nil && len(errs.Errors) > 0 { 220 return errs 221 } 222 223 return nil 224 } 225 226 func providerForName(name string) Provider { 227 switch name { 228 case "aws": 229 return new(AWSProvider) 230 case "scaleway": 231 return new(ScalewayProvider) 232 case "digitalocean": 233 return new(DigitalOceanProvider) 234 case "virtualbox": 235 return new(VBoxProvider) 236 case "vmware": 237 return new(VMwareProvider) 238 case "parallels": 239 return new(ParallelsProvider) 240 case "hyperv": 241 return new(HypervProvider) 242 case "libvirt": 243 return new(LibVirtProvider) 244 case "google": 245 return new(GoogleProvider) 246 case "lxc": 247 return new(LXCProvider) 248 case "azure": 249 return new(AzureProvider) 250 case "docker": 251 return new(DockerProvider) 252 default: 253 return nil 254 } 255 } 256 257 // OutputPathTemplate is the structure that is available within the 258 // OutputPath variables. 259 type outputPathTemplate struct { 260 ArtifactId string 261 BuildName string 262 Provider string 263 } 264 265 type vagrantfileTemplate struct { 266 ProviderVagrantfile string 267 CustomVagrantfile string 268 } 269 270 const boxVagrantfileContents string = ` 271 # The contents below were provided by the Packer Vagrant post-processor 272 {{ .ProviderVagrantfile }} 273 274 # The contents below (if any) are custom contents provided by the 275 # Packer template during image build. 276 {{ .CustomVagrantfile }} 277 `