post-processor/vagrant: Can make AWS boxes!

This commit is contained in:
Mitchell Hashimoto 2013-06-26 18:55:11 -07:00
parent 8a609b67c1
commit f4c9f96085
6 changed files with 205 additions and 3 deletions

View File

@ -0,0 +1,40 @@
package vagrant
import (
"fmt"
"os"
)
const BuilderId = "mitchellh.post-processor.vagrant"
type Artifact struct {
Path string
Provider string
}
func NewArtifact(provider, path string) *Artifact {
return &Artifact{
Path: path,
Provider: provider,
}
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return []string{a.Path}
}
func (a *Artifact) Id() string {
return ""
}
func (a *Artifact) String() string {
return fmt.Sprintf("'%s' provider box: %s", a.Provider, a.Path)
}
func (a *Artifact) Destroy() error {
return os.Remove(a.Path)
}

View File

@ -0,0 +1,14 @@
package vagrant
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestArtifact_ImplementsArtifact(t *testing.T) {
var raw interface{}
raw = &Artifact{}
if _, ok := raw.(packer.Artifact); !ok {
t.Fatalf("Artifact should be a Artifact")
}
}

View File

@ -1,16 +1,90 @@
package vagrant
import (
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
)
type AWSBoxConfig struct {
OutputPath string `mapstructure:"output"`
}
type AWSVagrantfileTemplate struct {
Images map[string]string
}
type AWSBoxPostProcessor struct {
config AWSBoxConfig
}
func (p *AWSBoxPostProcessor) Configure(raw interface{}) error {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
}
return nil
}
func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
return nil, nil
// Determine the regions...
tplData := &AWSVagrantfileTemplate{
Images: make(map[string]string),
}
for _, regions := range strings.Split(artifact.Id(), ",") {
parts := strings.Split(regions, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
}
tplData.Images[parts[0]] = parts[1]
}
// Create a temporary directory for us to build the contents of the box in
dir, err := ioutil.TempDir("", "packer")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir)
// Create the Vagrantfile from the template
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
if err != nil {
return nil, err
}
defer vf.Close()
t := template.Must(template.New("vagrantfile").Parse(defaultVagrantfile))
t.Execute(vf, tplData)
vf.Close()
// Create the metadata
metadata := map[string]string{"provider": "aws"}
if err := WriteMetadata(dir, metadata); err != nil {
return nil, err
}
// Compress the directory to the given output path
if err := DirToBox(p.config.OutputPath, dir); err != nil {
return nil, err
}
return NewArtifact("aws", p.config.OutputPath), nil
}
var defaultVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider "aws" do |aws|
{{ range $region, $ami := .Images }}
aws.region_config "{{ $region }}", ami: "{{ $ami }}"
{{ end }}
end
end
`

View File

@ -12,4 +12,3 @@ func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
t.Fatalf("AWS PostProcessor should be a PostProcessor")
}
}

View File

@ -11,7 +11,7 @@ var builtins = map[string]string{
"mitchellh.amazonebs": "aws",
}
type Config struct {}
type Config struct{}
type PostProcessor struct {
config Config

View File

@ -0,0 +1,75 @@
package vagrant
import (
"archive/tar"
"compress/gzip"
"encoding/json"
"io"
"os"
"path/filepath"
)
// DirToBox takes the directory and compresses it into a Vagrant-compatible
// box. This function does not perform checks to verify that dir is
// actually a proper box. This is an expected precondition.
func DirToBox(dst, dir string) error {
dstF, err := os.Create(dst)
if err != nil {
return err
}
defer dstF.Close()
gzipWriter := gzip.NewWriter(dstF)
defer gzipWriter.Close()
tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()
// This is the walk func that tars each of the files in the dir
tarWalk := func(path string, info os.FileInfo, prevErr error) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
// We have to set the Name explicitly because it is supposed to
// be a relative path to the root. Otherwise, the tar ends up
// being a bunch of files in the root, even if they're actually
// nested in a dir in the original "dir" param.
header.Name, err = filepath.Rel(dir, path)
if err != nil {
return err
}
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
if _, err := io.Copy(tarWriter, f); err != nil {
return err
}
return nil
}
// Tar.gz everything up
return filepath.Walk(dir, tarWalk)
}
// WriteMetadata writes the "metadata.json" file for a Vagrant box.
func WriteMetadata(dir string, contents interface{}) error {
f, err := os.Create(filepath.Join(dir, "metadata.json"))
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
return enc.Encode(contents)
}