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  `