From 7d4efdc23675c63f0856944290620d327bbbeaf6 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Mon, 16 Jun 2014 15:53:37 -0400 Subject: [PATCH 01/14] post-processor/vagrant-cloud: initial commit --- config.go | 3 +- plugin/post-processor-vagrant-cloud/main.go | 15 ++ .../post-processor-vagrant-cloud/main_test.go | 1 + post-processor/vagrant-cloud/api.go | 97 ++++++++++++ post-processor/vagrant-cloud/artifact.go | 40 +++++ post-processor/vagrant-cloud/artifact_test.go | 14 ++ .../vagrant-cloud/post-processor.go | 142 ++++++++++++++++++ .../vagrant-cloud/post-processor_test.go | 41 +++++ post-processor/vagrant/artifact.go | 2 +- post-processor/vagrant/artifact_test.go | 7 + 10 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 plugin/post-processor-vagrant-cloud/main.go create mode 100644 plugin/post-processor-vagrant-cloud/main_test.go create mode 100644 post-processor/vagrant-cloud/api.go create mode 100644 post-processor/vagrant-cloud/artifact.go create mode 100644 post-processor/vagrant-cloud/artifact_test.go create mode 100644 post-processor/vagrant-cloud/post-processor.go create mode 100644 post-processor/vagrant-cloud/post-processor_test.go diff --git a/config.go b/config.go index 9f1485aa8..44d362d26 100644 --- a/config.go +++ b/config.go @@ -47,7 +47,8 @@ const defaultConfig = ` "vagrant": "packer-post-processor-vagrant", "vsphere": "packer-post-processor-vsphere", "docker-push": "packer-post-processor-docker-push", - "docker-import": "packer-post-processor-docker-import" + "docker-import": "packer-post-processor-docker-import", + "vagrant-cloud": "packer-post-processor-vagrant-cloud" }, "provisioners": { diff --git a/plugin/post-processor-vagrant-cloud/main.go b/plugin/post-processor-vagrant-cloud/main.go new file mode 100644 index 000000000..b5e3e7044 --- /dev/null +++ b/plugin/post-processor-vagrant-cloud/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/vagrant-cloud" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(vagrantcloud.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-vagrant-cloud/main_test.go b/plugin/post-processor-vagrant-cloud/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-vagrant-cloud/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/vagrant-cloud/api.go b/post-processor/vagrant-cloud/api.go new file mode 100644 index 000000000..ba809c358 --- /dev/null +++ b/post-processor/vagrant-cloud/api.go @@ -0,0 +1,97 @@ +package vagrantcloud + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" +) + +type Box struct { + Tag string `json:"tag"` +} + +type VagrantCloudClient struct { + // The http client for communicating + client *http.Client + + // The base URL of the API + BaseURL string + + // Access token + AccessToken string +} + +func (v VagrantCloudClient) New(baseUrl string, token string) *VagrantCloudClient { + c := &VagrantCloudClient{ + client: &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + }, + }, + BaseURL: baseUrl, + AccessToken: token, + } + return c +} + +func decodeBody(resp *http.Response, out interface{}) error { + defer resp.Body.Close() + dec := json.NewDecoder(resp.Body) + return dec.Decode(out) +} + +// encodeBody is used to encode a request body +func encodeBody(obj interface{}) (io.Reader, error) { + buf := bytes.NewBuffer(nil) + enc := json.NewEncoder(buf) + if err := enc.Encode(obj); err != nil { + return nil, err + } + return buf, nil +} + +func (v VagrantCloudClient) Box(tag string) (*Box, error) { + resp, err := v.Get(tag) + + if err != nil { + return nil, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return nil, fmt.Errorf("Error parsing box response: %s", err) + } + + return box, nil +} + +func (v VagrantCloudClient) Get(path string) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + + // Scrub API key for logs + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) + + req, err := http.NewRequest("GET", reqUrl, nil) + resp, err := v.client.Do(req) + + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) + + return resp, err +} + +func (v VagrantCloudClient) Post(path string, body map[string]interface{}) (map[string]interface{}, error) { + + // Scrub API key for logs + scrubbedUrl := strings.Replace(path, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, body) + return nil, nil +} diff --git a/post-processor/vagrant-cloud/artifact.go b/post-processor/vagrant-cloud/artifact.go new file mode 100644 index 000000000..cebef8696 --- /dev/null +++ b/post-processor/vagrant-cloud/artifact.go @@ -0,0 +1,40 @@ +package vagrantcloud + +import ( + "fmt" + "os" +) + +const BuilderId = "pearkes.post-processor.vagrant-cloud" + +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) +} diff --git a/post-processor/vagrant-cloud/artifact_test.go b/post-processor/vagrant-cloud/artifact_test.go new file mode 100644 index 000000000..b95e04511 --- /dev/null +++ b/post-processor/vagrant-cloud/artifact_test.go @@ -0,0 +1,14 @@ +package vagrantcloud + +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") + } +} diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go new file mode 100644 index 000000000..b62542c6d --- /dev/null +++ b/post-processor/vagrant-cloud/post-processor.go @@ -0,0 +1,142 @@ +// vagrant_cloud implements the packer.PostProcessor interface and adds a +// post-processor that uploads artifacts from the vagrant post-processor +// to Vagrant Cloud (vagrantcloud.com) +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" +) + +const VAGRANT_CLOUD_URL = "https://vagrantcloud.com" + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Tag string `mapstructure:"box_tag"` + Version string `mapstructure:"version"` + + AccessToken string `mapstructure:"access_token"` + VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + config Config + client *VagrantCloudClient +} + +func (p *PostProcessor) Configure(raws ...interface{}) error { + _, err := common.DecodeConfig(&p.config, raws...) + if err != nil { + return err + } + + p.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return err + } + p.config.tpl.UserVars = p.config.PackerUserVars + + // Default configuration + if p.config.VagrantCloudUrl == "" { + p.config.VagrantCloudUrl = VAGRANT_CLOUD_URL + } + + // Accumulate any errors + errs := new(packer.MultiError) + + // required configuration + templates := map[string]*string{ + "box_tag": &p.config.Tag, + "version": &p.config.Version, + "access_token": &p.config.AccessToken, + } + + for key, ptr := range templates { + if *ptr == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("%s must be set", key)) + } + } + + // Template process + for key, ptr := range templates { + *ptr, err = p.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", key, err)) + } + } + + if len(errs.Errors) > 0 { + return errs + } + + return nil +} + +func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + config := p.config + + // Only accepts input from the vagrant post-processor + if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { + return nil, false, fmt.Errorf( + "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) + } + + // The name of the provider for vagrant cloud, and vagrant + provider := providerFromBuilderName(artifact.Id()) + version := p.config.Version + tag := p.config.Tag + + // create the HTTP client + p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) + + ui.Say(fmt.Sprintf("Verifying box is accessible: %s", tag)) + + box, err := p.client.Box(tag) + + if err != nil { + return nil, false, err + } + + if box.Tag != tag { + ui.Say(fmt.Sprintf("Could not verify box is correct: %s", tag)) + return nil, false, err + } + + ui.Say(fmt.Sprintf("Creating Version %s", version)) + ui.Say(fmt.Sprintf("Creating Provider %s", version)) + ui.Say(fmt.Sprintf("Uploading Box %s", version)) + ui.Say(fmt.Sprintf("Verifying upload %s", version)) + ui.Say(fmt.Sprintf("Releasing version %s", version)) + + return NewArtifact(provider, config.Tag), true, nil +} + +// Runs a cleanup if the post processor fails to upload +func (p *PostProcessor) Cleanup() { + // Delete the version +} + +// converts a packer builder name to the corresponding vagrant +// provider +func providerFromBuilderName(name string) string { + switch name { + case "aws": + return "aws" + case "digitalocean": + return "digitalocean" + case "virtualbox": + return "virtualbox" + case "vmware": + return "vmware_desktop" + case "parallels": + return "parallels" + default: + return name + } +} diff --git a/post-processor/vagrant-cloud/post-processor_test.go b/post-processor/vagrant-cloud/post-processor_test.go new file mode 100644 index 000000000..1a302fc4c --- /dev/null +++ b/post-processor/vagrant-cloud/post-processor_test.go @@ -0,0 +1,41 @@ +package vagrantcloud + +import ( + "bytes" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{} +} + +func testPP(t *testing.T) *PostProcessor { + var p PostProcessor + if err := p.Configure(testConfig()); err != nil { + t.Fatalf("err: %s", err) + } + + return &p +} + +func testUi() *packer.BasicUi { + return &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + } +} + +func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { + var _ packer.PostProcessor = new(PostProcessor) +} + +func TestproviderFromBuilderName(t *testing.T) { + if providerFromBuilderName("foobar") != "foobar" { + t.Fatal("should copy unknown provider") + } + + if providerFromBuilderName("vmware") != "vmware_desktop" { + t.Fatal("should convert provider") + } +} diff --git a/post-processor/vagrant/artifact.go b/post-processor/vagrant/artifact.go index 1b4885b52..c4cfe394b 100644 --- a/post-processor/vagrant/artifact.go +++ b/post-processor/vagrant/artifact.go @@ -28,7 +28,7 @@ func (a *Artifact) Files() []string { } func (a *Artifact) Id() string { - return "" + return a.Provider } func (a *Artifact) String() string { diff --git a/post-processor/vagrant/artifact_test.go b/post-processor/vagrant/artifact_test.go index 5c711dad2..6e16285a2 100644 --- a/post-processor/vagrant/artifact_test.go +++ b/post-processor/vagrant/artifact_test.go @@ -12,3 +12,10 @@ func TestArtifact_ImplementsArtifact(t *testing.T) { t.Fatalf("Artifact should be a Artifact") } } + +func TestArtifact_Id(t *testing.T) { + artifact := NewArtifact("vmware", "./") + if artifact.Id() != "vmware" { + t.Fatalf("should return name as Id") + } +} From a67836270166ba2402ead2b05622ec7f203bca8e Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Fri, 20 Jun 2014 11:20:27 -0400 Subject: [PATCH 02/14] post-processor/vagrant-cloud: add api items --- post-processor/vagrant-cloud/box.go | 44 ++++++++++++++ .../vagrant-cloud/{api.go => client.go} | 55 +++++++++++------- .../vagrant-cloud/post-processor.go | 13 ++++- post-processor/vagrant-cloud/provider.go | 44 ++++++++++++++ post-processor/vagrant-cloud/version.go | 58 +++++++++++++++++++ 5 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 post-processor/vagrant-cloud/box.go rename post-processor/vagrant-cloud/{api.go => client.go} (58%) create mode 100644 post-processor/vagrant-cloud/provider.go create mode 100644 post-processor/vagrant-cloud/version.go diff --git a/post-processor/vagrant-cloud/box.go b/post-processor/vagrant-cloud/box.go new file mode 100644 index 000000000..e8a67d44c --- /dev/null +++ b/post-processor/vagrant-cloud/box.go @@ -0,0 +1,44 @@ +package vagrantcloud + +import ( + "fmt" +) + +type Box struct { + client *VagrantCloudClient + Tag string `json:"tag"` +} + +// https://vagrantcloud.com/docs/boxes +func (v VagrantCloudClient) Box(tag string) (*Box, error) { + resp, err := v.Get(tag) + + if err != nil { + return nil, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return nil, fmt.Errorf("Error parsing box response: %s", err) + } + + return box, nil +} + +// Save persist the provider over HTTP to Vagrant Cloud +func (b Box) Save(tag string) (bool, error) { + resp, err := b.client.Get(tag) + + if err != nil { + return false, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} diff --git a/post-processor/vagrant-cloud/api.go b/post-processor/vagrant-cloud/client.go similarity index 58% rename from post-processor/vagrant-cloud/api.go rename to post-processor/vagrant-cloud/client.go index ba809c358..1dba74a94 100644 --- a/post-processor/vagrant-cloud/api.go +++ b/post-processor/vagrant-cloud/client.go @@ -11,10 +11,6 @@ import ( "strings" ) -type Box struct { - Tag string `json:"tag"` -} - type VagrantCloudClient struct { // The http client for communicating client *http.Client @@ -55,32 +51,33 @@ func encodeBody(obj interface{}) (io.Reader, error) { return buf, nil } -func (v VagrantCloudClient) Box(tag string) (*Box, error) { - resp, err := v.Get(tag) +func (v VagrantCloudClient) Get(path string) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) - if err != nil { - return nil, fmt.Errorf("Error retrieving box: %s", err) - } + // Scrub API key for logs + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) - box := &Box{} + req, err := http.NewRequest("GET", reqUrl, nil) + resp, err := v.client.Do(req) - if err = decodeBody(resp, box); err != nil { - return nil, fmt.Errorf("Error parsing box response: %s", err) - } + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) - return box, nil + return resp, err } -func (v VagrantCloudClient) Get(path string) (*http.Response, error) { +func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) // Scrub API key for logs scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) - log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) + log.Printf("Post-Processor Vagrant Cloud API DELETE: %s", scrubbedUrl) - req, err := http.NewRequest("GET", reqUrl, nil) + req, err := http.NewRequest("DELETE", reqUrl, nil) resp, err := v.client.Do(req) log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) @@ -88,10 +85,26 @@ func (v VagrantCloudClient) Get(path string) (*http.Response, error) { return resp, err } -func (v VagrantCloudClient) Post(path string, body map[string]interface{}) (map[string]interface{}, error) { +func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + + encBody, err := encodeBody(body) + + if err != nil { + return nil, fmt.Errorf("Error encoding body for request: %s", err) + } // Scrub API key for logs - scrubbedUrl := strings.Replace(path, v.AccessToken, "ACCESS_TOKEN", -1) - log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, body) - return nil, nil + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, encBody) + + req, err := http.NewRequest("POST", reqUrl, encBody) + + resp, err := v.client.Do(req) + + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) + + return resp, err } diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index b62542c6d..8b2eda710 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -81,6 +81,8 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { config := p.config + fmt.Println(artifact) + // Only accepts input from the vagrant post-processor if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { return nil, false, fmt.Errorf( @@ -89,7 +91,6 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // The name of the provider for vagrant cloud, and vagrant provider := providerFromBuilderName(artifact.Id()) - version := p.config.Version tag := p.config.Tag // create the HTTP client @@ -108,7 +109,14 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, err } - ui.Say(fmt.Sprintf("Creating Version %s", version)) + ui.Say(fmt.Sprintf("Creating Version %s", p.config.Version)) + + // Create the new version for the box + version := Version{Version: p.config.Version} + if ok, err := version.Create(); !ok { + return nil, false, err + } + ui.Say(fmt.Sprintf("Creating Provider %s", version)) ui.Say(fmt.Sprintf("Uploading Box %s", version)) ui.Say(fmt.Sprintf("Verifying upload %s", version)) @@ -120,6 +128,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // Runs a cleanup if the post processor fails to upload func (p *PostProcessor) Cleanup() { // Delete the version + } // converts a packer builder name to the corresponding vagrant diff --git a/post-processor/vagrant-cloud/provider.go b/post-processor/vagrant-cloud/provider.go new file mode 100644 index 000000000..d8c3d201e --- /dev/null +++ b/post-processor/vagrant-cloud/provider.go @@ -0,0 +1,44 @@ +package vagrantcloud + +import ( + "fmt" +) + +type Provider struct { + client *VagrantCloudClient + Name string `json:"name"` +} + +// https://vagrantcloud.com/docs/providers +func (v VagrantCloudClient) Provider(tag string) (*Box, error) { + resp, err := v.Get(tag) + + if err != nil { + return nil, fmt.Errorf("Error retrieving box: %s", err) + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + return nil, fmt.Errorf("Error parsing box response: %s", err) + } + + return box, nil +} + +// Save persist the box over HTTP to Vagrant Cloud +func (p Provider) Save(name string) (bool, error) { + resp, err := p.client.Get(name) + + if err != nil { + return false, fmt.Errorf("Error retrieving box: %s", err) + } + + provider := &Provider{} + + if err = decodeBody(resp, provider); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} diff --git a/post-processor/vagrant-cloud/version.go b/post-processor/vagrant-cloud/version.go new file mode 100644 index 000000000..5558f2967 --- /dev/null +++ b/post-processor/vagrant-cloud/version.go @@ -0,0 +1,58 @@ +package vagrantcloud + +import ( + "fmt" +) + +type Version struct { + client *VagrantCloudClient + Version string `json:"version"` + Number string `json:"number"` +} + +// https://vagrantcloud.com/docs/versions +func (v VagrantCloudClient) Version(number string) (*Version, error) { + resp, err := v.Get(number) + + if err != nil { + return nil, fmt.Errorf("Error retrieving version: %s", err) + } + + version := &Version{} + + if err = decodeBody(resp, version); err != nil { + return nil, fmt.Errorf("Error parsing version response: %s", err) + } + + return version, nil +} + +// Save persists the Version over HTTP to Vagrant Cloud +func (v Version) Create() (bool, error) { + resp, err := v.client.Post(v.Number, v) + + if err != nil { + return false, fmt.Errorf("Error retrieving box: %s", err) + } + + if err = decodeBody(resp, v); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} + +// Deletes the Version over HTTP to Vagrant Cloud +func (v Version) Destroy() (bool, error) { + resp, err := v.client.Delete(v.Number) + + if err != nil { + return false, fmt.Errorf("Error destroying version: %s", err) + } + + if err = decodeBody(resp, v); err != nil { + return false, fmt.Errorf("Error parsing box response: %s", err) + } + + return true, nil +} From c899051c9cd4f54c79d3a6ca12d4e3bfefdc138d Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Mon, 23 Jun 2014 15:48:51 -0400 Subject: [PATCH 03/14] post-processor/vagrant-cloud: use multistep --- post-processor/vagrant-cloud/box.go | 44 ----------- post-processor/vagrant-cloud/client.go | 24 +++++- .../vagrant-cloud/post-processor.go | 70 +++++++++-------- .../vagrant-cloud/step_create_provider.go | 15 ++++ .../vagrant-cloud/step_create_version.go | 77 +++++++++++++++++++ .../vagrant-cloud/step_prepare_upload.go | 15 ++++ post-processor/vagrant-cloud/step_upload.go | 15 ++++ .../vagrant-cloud/step_verify_box.go | 52 +++++++++++++ .../vagrant-cloud/step_verify_upload.go | 15 ++++ post-processor/vagrant-cloud/version.go | 58 -------------- 10 files changed, 249 insertions(+), 136 deletions(-) delete mode 100644 post-processor/vagrant-cloud/box.go create mode 100644 post-processor/vagrant-cloud/step_create_provider.go create mode 100644 post-processor/vagrant-cloud/step_create_version.go create mode 100644 post-processor/vagrant-cloud/step_prepare_upload.go create mode 100644 post-processor/vagrant-cloud/step_upload.go create mode 100644 post-processor/vagrant-cloud/step_verify_box.go create mode 100644 post-processor/vagrant-cloud/step_verify_upload.go delete mode 100644 post-processor/vagrant-cloud/version.go diff --git a/post-processor/vagrant-cloud/box.go b/post-processor/vagrant-cloud/box.go deleted file mode 100644 index e8a67d44c..000000000 --- a/post-processor/vagrant-cloud/box.go +++ /dev/null @@ -1,44 +0,0 @@ -package vagrantcloud - -import ( - "fmt" -) - -type Box struct { - client *VagrantCloudClient - Tag string `json:"tag"` -} - -// https://vagrantcloud.com/docs/boxes -func (v VagrantCloudClient) Box(tag string) (*Box, error) { - resp, err := v.Get(tag) - - if err != nil { - return nil, fmt.Errorf("Error retrieving box: %s", err) - } - - box := &Box{} - - if err = decodeBody(resp, box); err != nil { - return nil, fmt.Errorf("Error parsing box response: %s", err) - } - - return box, nil -} - -// Save persist the provider over HTTP to Vagrant Cloud -func (b Box) Save(tag string) (bool, error) { - resp, err := b.client.Get(tag) - - if err != nil { - return false, fmt.Errorf("Error retrieving box: %s", err) - } - - box := &Box{} - - if err = decodeBody(resp, box); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index 1dba74a94..fe47d8471 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -22,6 +22,19 @@ type VagrantCloudClient struct { AccessToken string } +type VagrantCloudErrors struct { + Errors map[string][]string `json:"errors"` +} + +func (v VagrantCloudErrors) FormatErrors() string { + errs := make([]string, 0) + for e := range v.Errors { + msg := fmt.Sprintf("%s %s", e, strings.Join(v.Errors[e], ",")) + errs = append(errs, msg) + } + return strings.Join(errs, ". ") +} + func (v VagrantCloudClient) New(baseUrl string, token string) *VagrantCloudClient { c := &VagrantCloudClient{ client: &http.Client{ @@ -54,7 +67,7 @@ func encodeBody(obj interface{}) (io.Reader, error) { func (v VagrantCloudClient) Get(path string) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) - reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) // Scrub API key for logs scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) @@ -71,7 +84,7 @@ func (v VagrantCloudClient) Get(path string) (*http.Response, error) { func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) - reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) // Scrub API key for logs scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) @@ -88,10 +101,14 @@ func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) - reqUrl := fmt.Sprintf("%s/%s%s", v.BaseURL, path, params.Encode()) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) + + log.Println(reqUrl) encBody, err := encodeBody(body) + log.Println(encBody) + if err != nil { return nil, fmt.Errorf("Error encoding body for request: %s", err) } @@ -101,6 +118,7 @@ func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", scrubbedUrl, encBody) req, err := http.NewRequest("POST", reqUrl, encBody) + req.Header.Add("Content-Type", "application/json") resp, err := v.client.Do(req) diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 8b2eda710..9e1e261ca 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -5,11 +5,13 @@ package vagrantcloud import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" + "log" ) -const VAGRANT_CLOUD_URL = "https://vagrantcloud.com" +const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1" type Config struct { common.PackerConfig `mapstructure:",squash"` @@ -26,6 +28,7 @@ type Config struct { type PostProcessor struct { config Config client *VagrantCloudClient + runner multistep.Runner } func (p *PostProcessor) Configure(raws ...interface{}) error { @@ -79,56 +82,61 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { - config := p.config - - fmt.Println(artifact) - // Only accepts input from the vagrant post-processor if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { return nil, false, fmt.Errorf( "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) } - // The name of the provider for vagrant cloud, and vagrant - provider := providerFromBuilderName(artifact.Id()) - tag := p.config.Tag - // create the HTTP client p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) - ui.Say(fmt.Sprintf("Verifying box is accessible: %s", tag)) - - box, err := p.client.Box(tag) - - if err != nil { - return nil, false, err + // Set up the state + state := new(multistep.BasicStateBag) + state.Put("config", p.config) + state.Put("client", p.client) + state.Put("artifact", artifact) + state.Put("ui", ui) + + // Build the steps + steps := []multistep.Step{ + new(stepVerifyBox), + new(stepCreateVersion), + new(stepCreateProvider), + new(stepPrepareUpload), + new(stepUpload), + new(stepVerifyUpload), } - if box.Tag != tag { - ui.Say(fmt.Sprintf("Could not verify box is correct: %s", tag)) - return nil, false, err + // Run the steps + if p.config.PackerDebug { + p.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + p.runner = &multistep.BasicRunner{Steps: steps} } - ui.Say(fmt.Sprintf("Creating Version %s", p.config.Version)) + p.runner.Run(state) - // Create the new version for the box - version := Version{Version: p.config.Version} - if ok, err := version.Create(); !ok { - return nil, false, err + // If there was an error, return that + if rawErr, ok := state.GetOk("error"); ok { + return nil, false, rawErr.(error) } - ui.Say(fmt.Sprintf("Creating Provider %s", version)) - ui.Say(fmt.Sprintf("Uploading Box %s", version)) - ui.Say(fmt.Sprintf("Verifying upload %s", version)) - ui.Say(fmt.Sprintf("Releasing version %s", version)) + // // The name of the provider for vagrant cloud, and vagrant + provider := providerFromBuilderName(artifact.Id()) - return NewArtifact(provider, config.Tag), true, nil + return NewArtifact(provider, p.config.Tag), true, nil } // Runs a cleanup if the post processor fails to upload -func (p *PostProcessor) Cleanup() { - // Delete the version - +func (p *PostProcessor) Cancel() { + if p.runner != nil { + log.Println("Cancelling the step runner...") + p.runner.Cancel() + } } // converts a packer builder name to the corresponding vagrant diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go new file mode 100644 index 000000000..7816f5016 --- /dev/null +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepCreateProvider struct { +} + +func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go new file mode 100644 index 000000000..054baacd0 --- /dev/null +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -0,0 +1,77 @@ +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type Version struct { + Version string `json:"version"` + Number uint `json:"number"` +} + +type NewVersion struct { + Version string `json:"version"` +} + +type stepCreateVersion struct { + number uint // number of the version, if needed in cleanup +} + +func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + box := state.Get("box").(*Box) + + path := fmt.Sprintf("box/%s/versions", box.Tag) + + // Wrap the version in a version object for the API + wrapper := make(map[string]interface{}) + wrapper["version"] = NewVersion{Version: config.Version} + + ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) + + resp, err := client.Post(path, wrapper) + version := &Version{} + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{}; + err = decodeBody(resp, cloudErrors); + state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + if err = decodeBody(resp, version); err != nil { + state.Put("error", fmt.Errorf("Error parsing version response: %s", err)) + return multistep.ActionHalt + } + + // Save the number for cleanup + s.number = version.Number + + state.Put("version", version) + + return multistep.ActionContinue +} + +func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { + // If we didn't save the version number, it likely doesn't exist + if (s.number == 0) { + return + } + + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + + path := fmt.Sprintf("box/%s/version/%s", box.Tag, s.number) + + // No need for resp from the cleanup DELETE + _, err := client.Delete(path) + + if err != nil { + ui.Error(fmt.Sprintf("Error destroying version: %s", err)) + } +} diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go new file mode 100644 index 000000000..1b3d61ac3 --- /dev/null +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepPrepareUpload struct { +} + +func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go new file mode 100644 index 000000000..5f3b27a55 --- /dev/null +++ b/post-processor/vagrant-cloud/step_upload.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepUpload struct { +} + +func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepUpload) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go new file mode 100644 index 000000000..784f7f7e7 --- /dev/null +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -0,0 +1,52 @@ +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type Box struct { + Tag string `json:"tag"` +} + +type stepVerifyBox struct { +} + +func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + + ui.Say(fmt.Sprintf("Verifying box is accessible: %s", config.Tag)) + + path := fmt.Sprintf("box/%s", config.Tag) + resp, err := client.Get(path) + + if err != nil { + state.Put("error", fmt.Errorf("Error retrieving box: %s", err)) + return multistep.ActionHalt + } + + box := &Box{} + + if err = decodeBody(resp, box); err != nil { + state.Put("error", fmt.Errorf("Error parsing box response: %s", err)) + return multistep.ActionHalt + } + + if box.Tag != config.Tag { + state.Put("error", fmt.Errorf("Could not verify box: %s", err)) + return multistep.ActionHalt + } + + // Keep the box in state for later + state.Put("box", box) + + // Box exists and is accessible + return multistep.ActionContinue +} + +func (s *stepVerifyBox) Cleanup(state multistep.StateBag) { + // no cleanup needed +} diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go new file mode 100644 index 000000000..d665589ce --- /dev/null +++ b/post-processor/vagrant-cloud/step_verify_upload.go @@ -0,0 +1,15 @@ +package vagrantcloud + +import ( + "github.com/mitchellh/multistep" +) + +type stepVerifyUpload struct { +} + +func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { + return multistep.ActionContinue +} + +func (s *stepVerifyUpload) Cleanup(state multistep.StateBag) { +} diff --git a/post-processor/vagrant-cloud/version.go b/post-processor/vagrant-cloud/version.go deleted file mode 100644 index 5558f2967..000000000 --- a/post-processor/vagrant-cloud/version.go +++ /dev/null @@ -1,58 +0,0 @@ -package vagrantcloud - -import ( - "fmt" -) - -type Version struct { - client *VagrantCloudClient - Version string `json:"version"` - Number string `json:"number"` -} - -// https://vagrantcloud.com/docs/versions -func (v VagrantCloudClient) Version(number string) (*Version, error) { - resp, err := v.Get(number) - - if err != nil { - return nil, fmt.Errorf("Error retrieving version: %s", err) - } - - version := &Version{} - - if err = decodeBody(resp, version); err != nil { - return nil, fmt.Errorf("Error parsing version response: %s", err) - } - - return version, nil -} - -// Save persists the Version over HTTP to Vagrant Cloud -func (v Version) Create() (bool, error) { - resp, err := v.client.Post(v.Number, v) - - if err != nil { - return false, fmt.Errorf("Error retrieving box: %s", err) - } - - if err = decodeBody(resp, v); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} - -// Deletes the Version over HTTP to Vagrant Cloud -func (v Version) Destroy() (bool, error) { - resp, err := v.client.Delete(v.Number) - - if err != nil { - return false, fmt.Errorf("Error destroying version: %s", err) - } - - if err = decodeBody(resp, v); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} From 26abac69993178ae51926b12dd46fdfa2c377e4d Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Tue, 24 Jun 2014 15:58:45 -0400 Subject: [PATCH 04/14] post-processor/vagrant-cloud: steps for create, upload and release --- post-processor/vagrant-cloud/client.go | 63 +++++++++++++- .../vagrant-cloud/post-processor.go | 18 +++- post-processor/vagrant-cloud/provider.go | 44 ---------- .../vagrant-cloud/step_create_provider.go | 71 +++++++++++++++ .../vagrant-cloud/step_create_version.go | 76 +++++++++------- .../vagrant-cloud/step_prepare_upload.go | 37 ++++++++ .../vagrant-cloud/step_release_version.go | 36 ++++++++ post-processor/vagrant-cloud/step_upload.go | 18 ++++ .../vagrant-cloud/step_verify_box.go | 12 ++- .../vagrant-cloud/step_verify_upload.go | 87 ++++++++++++++++++- 10 files changed, 379 insertions(+), 83 deletions(-) delete mode 100644 post-processor/vagrant-cloud/provider.go create mode 100644 post-processor/vagrant-cloud/step_release_version.go diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index fe47d8471..19b029576 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -6,8 +6,11 @@ import ( "fmt" "io" "log" + "mime/multipart" "net/http" "net/url" + "os" + "path/filepath" "strings" ) @@ -74,6 +77,7 @@ func (v VagrantCloudClient) Get(path string) (*http.Response, error) { log.Printf("Post-Processor Vagrant Cloud API GET: %s", scrubbedUrl) req, err := http.NewRequest("GET", reqUrl, nil) + req.Header.Add("Content-Type", "application/json") resp, err := v.client.Do(req) log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) @@ -91,6 +95,7 @@ func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { log.Printf("Post-Processor Vagrant Cloud API DELETE: %s", scrubbedUrl) req, err := http.NewRequest("DELETE", reqUrl, nil) + req.Header.Add("Content-Type", "application/json") resp, err := v.client.Do(req) log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) @@ -98,13 +103,48 @@ func (v VagrantCloudClient) Delete(path string) (*http.Response, error) { return resp, err } +func (v VagrantCloudClient) Upload(path string, url string) (*http.Response, error) { + file, err := os.Open(path) + + if err != nil { + return nil, fmt.Errorf("Error opening file for upload: %s", err) + } + + defer file.Close() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", filepath.Base(path)) + if err != nil { + return nil, err + } + + _, err = io.Copy(part, file) + + if err != nil { + return nil, fmt.Errorf("Error uploading file: %s", err) + } + + request, err := http.NewRequest("PUT", url, body) + + if err != nil { + return nil, fmt.Errorf("Error preparing upload request: %s", err) + } + + log.Printf("Post-Processor Vagrant Cloud API Upload: %s %s", path, url) + + resp, err := v.client.Do(request) + + log.Printf("Post-Processor Vagrant Cloud Upload Response: \n\n%s", resp) + + return resp, err +} + func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) { params := url.Values{} params.Set("access_token", v.AccessToken) reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) - log.Println(reqUrl) - encBody, err := encodeBody(body) log.Println(encBody) @@ -126,3 +166,22 @@ func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, return resp, err } + +func (v VagrantCloudClient) Put(path string) (*http.Response, error) { + params := url.Values{} + params.Set("access_token", v.AccessToken) + reqUrl := fmt.Sprintf("%s/%s?%s", v.BaseURL, path, params.Encode()) + + // Scrub API key for logs + scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1) + log.Printf("Post-Processor Vagrant Cloud API PUT: %s", scrubbedUrl) + + req, err := http.NewRequest("PUT", reqUrl, nil) + req.Header.Add("Content-Type", "application/json") + + resp, err := v.client.Do(req) + + log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%s", resp) + + return resp, err +} diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 9e1e261ca..a655819f0 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -9,6 +9,7 @@ import ( "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "log" + "strings" ) const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1" @@ -88,15 +89,26 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) } + // We assume that there is only one .box file to upload + if !strings.HasSuffix(artifact.Files()[0], ".box") { + return nil, false, fmt.Errorf( + "Unknown files in artifact from vagrant post-processor: %s", artifact.Files()) + } + // create the HTTP client p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) + // The name of the provider for vagrant cloud, and vagrant + providerName := providerFromBuilderName(artifact.Id()) + // Set up the state state := new(multistep.BasicStateBag) state.Put("config", p.config) state.Put("client", p.client) state.Put("artifact", artifact) + state.Put("artifactFilePath", artifact.Files()[0]) state.Put("ui", ui) + state.Put("providerName", providerName) // Build the steps steps := []multistep.Step{ @@ -106,6 +118,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac new(stepPrepareUpload), new(stepUpload), new(stepVerifyUpload), + new(stepReleaseVersion), } // Run the steps @@ -125,10 +138,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, rawErr.(error) } - // // The name of the provider for vagrant cloud, and vagrant - provider := providerFromBuilderName(artifact.Id()) - - return NewArtifact(provider, p.config.Tag), true, nil + return NewArtifact(providerName, p.config.Tag), true, nil } // Runs a cleanup if the post processor fails to upload diff --git a/post-processor/vagrant-cloud/provider.go b/post-processor/vagrant-cloud/provider.go deleted file mode 100644 index d8c3d201e..000000000 --- a/post-processor/vagrant-cloud/provider.go +++ /dev/null @@ -1,44 +0,0 @@ -package vagrantcloud - -import ( - "fmt" -) - -type Provider struct { - client *VagrantCloudClient - Name string `json:"name"` -} - -// https://vagrantcloud.com/docs/providers -func (v VagrantCloudClient) Provider(tag string) (*Box, error) { - resp, err := v.Get(tag) - - if err != nil { - return nil, fmt.Errorf("Error retrieving box: %s", err) - } - - box := &Box{} - - if err = decodeBody(resp, box); err != nil { - return nil, fmt.Errorf("Error parsing box response: %s", err) - } - - return box, nil -} - -// Save persist the box over HTTP to Vagrant Cloud -func (p Provider) Save(name string) (bool, error) { - resp, err := p.client.Get(name) - - if err != nil { - return false, fmt.Errorf("Error retrieving box: %s", err) - } - - provider := &Provider{} - - if err = decodeBody(resp, provider); err != nil { - return false, fmt.Errorf("Error parsing box response: %s", err) - } - - return true, nil -} diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go index 7816f5016..e149ddba1 100644 --- a/post-processor/vagrant-cloud/step_create_provider.go +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -1,15 +1,86 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) +type Provider struct { + Name string `json:"name"` + HostedToken string `json:"hosted_token,omitempty"` + UploadUrl string `json:"upload_url,omitempty"` +} + type stepCreateProvider struct { + name string // the name of the provider } func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + providerName := state.Get("providerName").(string) + + path := fmt.Sprintf("box/%s/version/%v/providers", box.Tag, version.Number) + + provider := &Provider{Name: providerName} + + // Wrap the provider in a provider object for the API + wrapper := make(map[string]interface{}) + wrapper["provider"] = provider + + ui.Say(fmt.Sprintf("Creating provider: %s", providerName)) + + resp, err := client.Post(path, wrapper) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error creating provider: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + if err = decodeBody(resp, provider); err != nil { + state.Put("error", fmt.Errorf("Error parsing provider response: %s", err)) + return multistep.ActionHalt + } + + // Save the name for cleanup + s.name = provider.Name + + state.Put("provider", provider) + return multistep.ActionContinue } func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { + // If we didn't save the provider name, it likely doesn't exist + if s.name == "" { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + // Return if we didn't cancel or halt, and thus need + // no cleanup + if !cancelled && !halted { + return + } + + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + + path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Number, s.name) + + // No need for resp from the cleanup DELETE + _, err := client.Delete(path) + + if err != nil { + ui.Error(fmt.Sprintf("Error destroying provider: %s", err)) + } } diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 054baacd0..211604875 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -2,17 +2,13 @@ package vagrantcloud import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) type Version struct { Version string `json:"version"` - Number uint `json:"number"` -} - -type NewVersion struct { - Version string `json:"version"` + Number uint `json:"number,omitempty"` } type stepCreateVersion struct { @@ -25,53 +21,71 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(Config) box := state.Get("box").(*Box) - path := fmt.Sprintf("box/%s/versions", box.Tag) + if hasVersion, v := box.HasVersion(config.Version); hasVersion { + ui.Say(fmt.Sprintf("Version exists: %s", config.Version)) + state.Put("version", v) + return multistep.ActionContinue + } + + path := fmt.Sprintf("box/%s/versions", box.Tag) + + version := &Version{Version: config.Version} // Wrap the version in a version object for the API wrapper := make(map[string]interface{}) - wrapper["version"] = NewVersion{Version: config.Version} + wrapper["version"] = version - ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) + ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) resp, err := client.Post(path, wrapper) - version := &Version{} if err != nil || (resp.StatusCode != 200) { - cloudErrors := &VagrantCloudErrors{}; - err = decodeBody(resp, cloudErrors); - state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors())) return multistep.ActionHalt } if err = decodeBody(resp, version); err != nil { state.Put("error", fmt.Errorf("Error parsing version response: %s", err)) - return multistep.ActionHalt + return multistep.ActionHalt } - // Save the number for cleanup - s.number = version.Number + // Save the number for cleanup + s.number = version.Number - state.Put("version", version) + state.Put("version", version) return multistep.ActionContinue } func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { - // If we didn't save the version number, it likely doesn't exist - if (s.number == 0) { - return - } + // If we didn't save the version number, it likely doesn't exist or + // already existed + if s.number == 0 { + return + } - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) - path := fmt.Sprintf("box/%s/version/%s", box.Tag, s.number) + // Return if we didn't cancel or halt, and thus need + // no cleanup + if !cancelled && !halted { + return + } - // No need for resp from the cleanup DELETE - _, err := client.Delete(path) + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + + path := fmt.Sprintf("box/%s/version/%v", box.Tag, s.number) + + // No need for resp from the cleanup DELETE + _, err := client.Delete(path) + + if err != nil { + ui.Error(fmt.Sprintf("Error destroying version: %s", err)) + } - if err != nil { - ui.Error(fmt.Sprintf("Error destroying version: %s", err)) - } } diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go index 1b3d61ac3..5c7fee2b9 100644 --- a/post-processor/vagrant-cloud/step_prepare_upload.go +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -1,15 +1,52 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) +type Upload struct { + Token string `json:"token"` + UploadPath string `json:"upload_path"` +} + type stepPrepareUpload struct { } func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + provider := state.Get("provider").(*Provider) + artifactFilePath := state.Get("artifactFilePath").(string) + + path := fmt.Sprintf("box/%s/version/%v/provider/%s/upload", box.Tag, version.Number, provider.Name) + upload := &Upload{} + + ui.Say(fmt.Sprintf("Preparing upload of box: %s", artifactFilePath)) + + resp, err := client.Get(path) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error preparing upload: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + if err = decodeBody(resp, upload); err != nil { + state.Put("error", fmt.Errorf("Error parsing upload response: %s", err)) + return multistep.ActionHalt + } + + // Save the upload details to the state + state.Put("upload", upload) + return multistep.ActionContinue } func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) { + // No cleanup } diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go new file mode 100644 index 000000000..0065c6100 --- /dev/null +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -0,0 +1,36 @@ +package vagrantcloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type stepReleaseVersion struct { +} + +func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + + path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number) + + ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) + + resp, err := client.Put(path) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error releasing version: %s", cloudErrors.FormatErrors())) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepReleaseVersion) Cleanup(state multistep.StateBag) { + // No cleanup +} diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index 5f3b27a55..4fb43028c 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -1,15 +1,33 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) type stepUpload struct { } func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + upload := state.Get("upload").(*Upload) + artifactFilePath := state.Get("artifactFilePath").(string) + url := upload.UploadPath + + ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath)) + + resp, err := client.Upload(artifactFilePath, url) + + if err != nil || (resp.StatusCode != 200) { + state.Put("error", fmt.Errorf("Error uploading Box: %s", resp.Body)) + return multistep.ActionHalt + } + return multistep.ActionContinue } func (s *stepUpload) Cleanup(state multistep.StateBag) { + // No cleanup } diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 784f7f7e7..95aaa0c0f 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -7,7 +7,17 @@ import ( ) type Box struct { - Tag string `json:"tag"` + Tag string `json:"tag"` + Versions []*Version `json:"versions"` +} + +func (b *Box) HasVersion(version string) (bool, *Version) { + for _, v := range b.Versions { + if v.Version == version { + return true, v + } + } + return false, nil } type stepVerifyBox struct { diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go index d665589ce..eb787886f 100644 --- a/post-processor/vagrant-cloud/step_verify_upload.go +++ b/post-processor/vagrant-cloud/step_verify_upload.go @@ -1,15 +1,100 @@ package vagrantcloud import ( + "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" ) type stepVerifyUpload struct { } func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { - return multistep.ActionContinue + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + upload := state.Get("upload").(*Upload) + provider := state.Get("provider").(*Provider) + + path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Number, provider.Name) + + providerCheck := &Provider{} + + ui.Say(fmt.Sprintf("Verifying provider upload: %s", provider.Name)) + + done := make(chan struct{}) + defer close(done) + + result := make(chan error, 1) + + go func() { + attempts := 0 + for { + attempts += 1 + + log.Printf("Checking token match for provider.. (attempt: %d)", attempts) + + resp, err := client.Get(path) + + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + err = fmt.Errorf("Error retrieving provider: %s", cloudErrors.FormatErrors()) + result <- err + return + } + + if err = decodeBody(resp, providerCheck); err != nil { + err = fmt.Errorf("Error parsing provider response: %s", err) + result <- err + return + } + + if err != nil { + result <- err + return + } + + if upload.Token == providerCheck.HostedToken { + // success! + result <- nil + return + } + + // Wait 3 seconds in between + time.Sleep(3 * time.Second) + + // Verify we shouldn't exit + select { + case <-done: + // We finished, so just exit the goroutine + return + default: + // Keep going + } + } + }() + + log.Printf("Waiting for up to 600 seconds for provider hosted token to match %s", upload.Token) + + select { + case err := <-result: + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + log.Printf("Box succesfully verified %s == %s", upload.Token, providerCheck.HostedToken) + return multistep.ActionContinue + case <-time.After(600 * time.Second): + state.Put("error", fmt.Errorf("Timeout while waiting to for upload to verify token '%s'", upload.Token)) + return multistep.ActionHalt + } } func (s *stepVerifyUpload) Cleanup(state multistep.StateBag) { + // No cleanup } From 979752cc573bad630c141b49d56b622cf5adf53b Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Tue, 24 Jun 2014 16:11:56 -0400 Subject: [PATCH 05/14] post-processor/vagrant-cloud: remove extra log statement --- post-processor/vagrant-cloud/client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/post-processor/vagrant-cloud/client.go b/post-processor/vagrant-cloud/client.go index 19b029576..783745c67 100644 --- a/post-processor/vagrant-cloud/client.go +++ b/post-processor/vagrant-cloud/client.go @@ -147,8 +147,6 @@ func (v VagrantCloudClient) Post(path string, body interface{}) (*http.Response, encBody, err := encodeBody(body) - log.Println(encBody) - if err != nil { return nil, fmt.Errorf("Error encoding body for request: %s", err) } From f3848068214d67e03a1a875633c9894c2fdc7b16 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 10:32:17 -0400 Subject: [PATCH 06/14] post-processor/vagrant-cloud: add no_release and version_description --- post-processor/vagrant-cloud/post-processor.go | 6 ++++-- post-processor/vagrant-cloud/step_create_version.go | 5 +++-- post-processor/vagrant-cloud/step_release_version.go | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index a655819f0..8a7f53ebc 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -17,8 +17,10 @@ const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1" type Config struct { common.PackerConfig `mapstructure:",squash"` - Tag string `mapstructure:"box_tag"` - Version string `mapstructure:"version"` + Tag string `mapstructure:"box_tag"` + Version string `mapstructure:"version"` + VersionDescription string `mapstructure:"version_description"` + NoRelease bool `mapstructure:"no_release"` AccessToken string `mapstructure:"access_token"` VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"` diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 211604875..882a5971d 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -8,7 +8,8 @@ import ( type Version struct { Version string `json:"version"` - Number uint `json:"number,omitempty"` + Description string `json:"description,omitempty"` + Number uint `json:"number,omitempty"` } type stepCreateVersion struct { @@ -29,7 +30,7 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { path := fmt.Sprintf("box/%s/versions", box.Tag) - version := &Version{Version: config.Version} + version := &Version{Version: config.Version, Description: config.VersionDescription} // Wrap the version in a version object for the API wrapper := make(map[string]interface{}) diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go index 0065c6100..4e40f5ca3 100644 --- a/post-processor/vagrant-cloud/step_release_version.go +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -14,6 +14,12 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction ui := state.Get("ui").(packer.Ui) box := state.Get("box").(*Box) version := state.Get("version").(*Version) + config := state.Get("config").(Config) + + if config.NoRelease { + ui.Say(fmt.Sprintf("Not releasing version due to configuration: %s", version.Version)) + return multistep.ActionContinue + } path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number) From a0c153824f2664d944efca9273a7f4963df1e2a7 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 10:56:09 -0400 Subject: [PATCH 07/14] post-processor/vagrant-cloud: tests for configuration --- .../vagrant-cloud/post-processor_test.go | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/post-processor/vagrant-cloud/post-processor_test.go b/post-processor/vagrant-cloud/post-processor_test.go index 1a302fc4c..ff99314c4 100644 --- a/post-processor/vagrant-cloud/post-processor_test.go +++ b/post-processor/vagrant-cloud/post-processor_test.go @@ -6,17 +6,35 @@ import ( "testing" ) -func testConfig() map[string]interface{} { - return map[string]interface{}{} +func testGoodConfig() map[string]interface{} { + return map[string]interface{}{ + "access_token": "foo", + "version_description": "bar", + "box_tag": "hashicorp/precise64", + "version": "0.5", + } +} + +func testBadConfig() map[string]interface{} { + return map[string]interface{}{ + "access_token": "foo", + "box_tag": "baz", + "version_description": "bar", + } } -func testPP(t *testing.T) *PostProcessor { +func TestPostProcessor_Configure_Good(t *testing.T) { var p PostProcessor - if err := p.Configure(testConfig()); err != nil { + if err := p.Configure(testGoodConfig()); err != nil { t.Fatalf("err: %s", err) } +} - return &p +func TestPostProcessor_Configure_Bad(t *testing.T) { + var p PostProcessor + if err := p.Configure(testBadConfig()); err == nil { + t.Fatalf("should have err") + } } func testUi() *packer.BasicUi { From 6a3c9921d2077df141e57b62d8d911a1243e8d56 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:29:25 -0400 Subject: [PATCH 08/14] post-processor/vagrant-cloud: better errors in box verifcation --- post-processor/vagrant-cloud/step_verify_box.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 95aaa0c0f..32ae9a5b6 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -33,8 +33,10 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { path := fmt.Sprintf("box/%s", config.Tag) resp, err := client.Get(path) - if err != nil { - state.Put("error", fmt.Errorf("Error retrieving box: %s", err)) + if err != nil || (resp.StatusCode != 200) { + cloudErrors := &VagrantCloudErrors{} + err = decodeBody(resp, cloudErrors) + state.Put("error", fmt.Errorf("Error retrieving box: %s", cloudErrors.FormatErrors())) return multistep.ActionHalt } @@ -46,7 +48,7 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { } if box.Tag != config.Tag { - state.Put("error", fmt.Errorf("Could not verify box: %s", err)) + state.Put("error", fmt.Errorf("Could not verify box: %s", config.Tag)) return multistep.ActionHalt } From 34164b25229718b6b2928ee6981ac5a0ac6d71f5 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:44:08 -0400 Subject: [PATCH 09/14] post-processor/vagrant-cloud: add docs --- .../vagrant-cloud.html.markdown | 102 ++++++++++++++++++ website/source/layouts/docs.erb | 1 + 2 files changed, 103 insertions(+) create mode 100644 website/source/docs/post-processors/vagrant-cloud.html.markdown diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown new file mode 100644 index 000000000..03d65442d --- /dev/null +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -0,0 +1,102 @@ +--- +layout: "docs" +page_title: "Vagrant Cloud Post-Processor" +--- + +# Vagrant Cloud Post-Processor + +Type: `vagrant-cloud` + +The Vagrant Cloud post-processor recieves a Vagrant box from the `vagrant` +post-processor and pushes it to Vagrant Cloud. [Vagrant Cloud](https://vagrantcloud.com) +hosts and serves boxes to Vagrant, allowing you to version and distribute +boxes to an organization in a simple way. + +You'll need to be familiar with Vagrant Cloud, have an upgraded account +to enable box hosting, and be distributing your box via the [shorthand name](http://docs.vagrantup.com/v2/cli/box.html) +configuration. + +## Workflow + +It's important to understand the workflow that using this post-processor +enforces in order to take full advantage of Vagrant and Vagrant Cloud. + +The use of this processor assume that you currently distribute, or plan +to distrubute, boxes via Vagrant Cloud. It also assumes you create Vagrant +Boxes and deliver them to your team in some fashion. + +Here is an example workflow: + +1. You use Packer to build a Vagrant Box for the `virtualbox` provider +2. The `vagrant-cloud` post-processor is configured to point to the box `hashicorp/foobar` on Vagrant Cloud +via the `box_tag` configuration +2. The post-processor receives the box from the `vagrant` post-processor +3. It then creates the configured version, or verifies the existence of it, on Vagrant Cloud +4. A provider matching the name of the Vagrant provider is then created +5. The box is uploaded to Vagrant Cloud +6. The upload is verified +7. The version is released and available to users of the box + + +## Configuration + +The configuration allows you to specify the target box that you have +access to on Vagrant Cloud, as well as authentication and version information. + +### Required: + +* `access_token` (string) - Your access token for the Vagrant Cloud API. + This can be generated on your [tokens page](https://vagrantcloud.com/account/tokens). + +* `box_tag` (string) - The shorthand tag for your box that maps to + Vagrant Cloud, i.e `hashicorp/precise64` for `vagrantcloud.com/hashicorp/precise64` + +* `version` (string) - The version number, typically incrementing a previous version. + The version string is validated based on [Semantic Versioning](http://semver.org/). The string must match + a pattern that could be semver, and doesn't validate that the version comes after + your previous versions. + + +### Optional: + +* `version_description` (string) - Optionally markdown text used as a full-length + and in-depth description of the version, typically for denoting changes introduced + +* `no_release` (string) - If set to true, does not release the version +on Vagrant Cloud, making it active. You can manually release the version +via the API or Web UI. Defaults to false. + + +## Use with Vagrant Post-Processor + +You'll need to use the Vagrant post-processor before using this post-processor. +An example configuration is below. Note the use of the array specifying +the execution order. + +```json +{ + "variables": { + "version": "", + "cloud_token": "" + }, + "builders": [{ + ... + }], + "post-processors": [ + [{ + "type": "vagrant", + "include": ["image.iso"], + "vagrantfile_template": "vagrantfile.tpl", + "output": "proxycore_{{.Provider}}.box", + }, + { + "type": "vagrant-cloud", + "box_tag": "hashicorp/precise64", + "access_token": "{{user `cloud_token`}}", + "version": "{{user `version`}}", + "version_description": "- added rabbitmq\n- modified db config\n- compacted log files" + }] + ] +} + +``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 83897136e..ea395d2f9 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -61,6 +61,7 @@
  • docker-import
  • docker-push
  • Vagrant
  • +
  • Vagrant Cloud
  • vSphere
  • From 560da2765161b5719788f1dfa92efe41a3ce9025 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:45:53 -0400 Subject: [PATCH 10/14] website: simplify example for vagrant cloud post processor --- .../source/docs/post-processors/vagrant-cloud.html.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index 03d65442d..cf2bb6ef0 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -93,8 +93,7 @@ the execution order. "type": "vagrant-cloud", "box_tag": "hashicorp/precise64", "access_token": "{{user `cloud_token`}}", - "version": "{{user `version`}}", - "version_description": "- added rabbitmq\n- modified db config\n- compacted log files" + "version": "{{user `version`}}" }] ] } From 5b2aaf3393960eed760a93e581f05602370f3ebf Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:46:27 -0400 Subject: [PATCH 11/14] website: correct example configuration for vagrant pp --- website/source/docs/post-processors/vagrant-cloud.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index cf2bb6ef0..d95ee9343 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -87,7 +87,7 @@ the execution order. "type": "vagrant", "include": ["image.iso"], "vagrantfile_template": "vagrantfile.tpl", - "output": "proxycore_{{.Provider}}.box", + "output": "proxycore_{{.Provider}}.box" }, { "type": "vagrant-cloud", From 450ba0bd9df80288342c26c1ef30a2c7cb557b82 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 11:54:49 -0400 Subject: [PATCH 12/14] post-processor/vagrant-cloud: improve error for upload failures --- post-processor/vagrant-cloud/step_upload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index 4fb43028c..0e8d20606 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -21,7 +21,7 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { resp, err := client.Upload(artifactFilePath, url) if err != nil || (resp.StatusCode != 200) { - state.Put("error", fmt.Errorf("Error uploading Box: %s", resp.Body)) + state.Put("error", fmt.Errorf("Error uploading Box: %s", err)) return multistep.ActionHalt } From 46535e3a3cb7147b0d194b5b7017457ae52bbb8e Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 12:46:25 -0400 Subject: [PATCH 13/14] post-processor/vagrant-cloud: better logging, document vcloud url --- .../vagrant-cloud/step_create_provider.go | 13 +++++++--- .../vagrant-cloud/step_create_version.go | 25 +++++++++++-------- .../vagrant-cloud/step_prepare_upload.go | 2 ++ .../vagrant-cloud/step_release_version.go | 8 +++--- post-processor/vagrant-cloud/step_upload.go | 4 +++ .../vagrant-cloud/step_verify_box.go | 2 ++ .../vagrant-cloud/step_verify_upload.go | 3 +++ .../vagrant-cloud.html.markdown | 3 +++ 8 files changed, 43 insertions(+), 17 deletions(-) diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go index e149ddba1..887932c4f 100644 --- a/post-processor/vagrant-cloud/step_create_provider.go +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -56,8 +56,15 @@ func (s *stepCreateProvider) Run(state multistep.StateBag) multistep.StepAction } func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + box := state.Get("box").(*Box) + version := state.Get("version").(*Version) + // If we didn't save the provider name, it likely doesn't exist if s.name == "" { + ui.Say("Cleaning up provider") + ui.Message("Provider was not created, not deleting") return } @@ -70,10 +77,8 @@ func (s *stepCreateProvider) Cleanup(state multistep.StateBag) { return } - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) - version := state.Get("version").(*Version) + ui.Say("Cleaning up provider") + ui.Message(fmt.Sprintf("Deleting provider: %s", s.name)) path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Number, s.name) diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 882a5971d..8108577cc 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -7,9 +7,9 @@ import ( ) type Version struct { - Version string `json:"version"` - Description string `json:"description,omitempty"` - Number uint `json:"number,omitempty"` + Version string `json:"version"` + Description string `json:"description,omitempty"` + Number uint `json:"number,omitempty"` } type stepCreateVersion struct { @@ -22,8 +22,10 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(Config) box := state.Get("box").(*Box) + ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) + if hasVersion, v := box.HasVersion(config.Version); hasVersion { - ui.Say(fmt.Sprintf("Version exists: %s", config.Version)) + ui.Message(fmt.Sprintf("Version exists, skipping creation")) state.Put("version", v) return multistep.ActionContinue } @@ -36,8 +38,6 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { wrapper := make(map[string]interface{}) wrapper["version"] = version - ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) - resp, err := client.Post(path, wrapper) if err != nil || (resp.StatusCode != 200) { @@ -61,9 +61,15 @@ func (s *stepCreateVersion) Run(state multistep.StateBag) multistep.StepAction { } func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { + client := state.Get("client").(*VagrantCloudClient) + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + box := state.Get("box").(*Box) + // If we didn't save the version number, it likely doesn't exist or // already existed if s.number == 0 { + ui.Message("Version was not created or previously existed, not deleting") return } @@ -76,12 +82,11 @@ func (s *stepCreateVersion) Cleanup(state multistep.StateBag) { return } - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) - path := fmt.Sprintf("box/%s/version/%v", box.Tag, s.number) + ui.Say("Cleaning up version") + ui.Message(fmt.Sprintf("Deleting version: %s", config.Version)) + // No need for resp from the cleanup DELETE _, err := client.Delete(path) diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go index 5c7fee2b9..5c82d02c3 100644 --- a/post-processor/vagrant-cloud/step_prepare_upload.go +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -41,6 +41,8 @@ func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message(fmt.Sprintf("Box upload prepared with token %s", upload.Token)) + // Save the upload details to the state state.Put("upload", upload) diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go index 4e40f5ca3..43512bdca 100644 --- a/post-processor/vagrant-cloud/step_release_version.go +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -16,15 +16,15 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction version := state.Get("version").(*Version) config := state.Get("config").(Config) + ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) + if config.NoRelease { - ui.Say(fmt.Sprintf("Not releasing version due to configuration: %s", version.Version)) + ui.Message("Not releasing version due to configuration") return multistep.ActionContinue } path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number) - ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) - resp, err := client.Put(path) if err != nil || (resp.StatusCode != 200) { @@ -34,6 +34,8 @@ func (s *stepReleaseVersion) Run(state multistep.StateBag) multistep.StepAction return multistep.ActionHalt } + ui.Message(fmt.Sprintf("Version successfully released and available")) + return multistep.ActionContinue } diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index 0e8d20606..f82f125f8 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -18,6 +18,8 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath)) + ui.Message("Depending on your internet connection and the size of the box, this may take some time") + resp, err := client.Upload(artifactFilePath, url) if err != nil || (resp.StatusCode != 200) { @@ -25,6 +27,8 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message("Box succesfully uploaded") + return multistep.ActionContinue } diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 32ae9a5b6..bbbc3b4b7 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -52,6 +52,8 @@ func (s *stepVerifyBox) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message("Box accessible and matches tag") + // Keep the box in state for later state.Put("box", box) diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go index eb787886f..120bd647d 100644 --- a/post-processor/vagrant-cloud/step_verify_upload.go +++ b/post-processor/vagrant-cloud/step_verify_upload.go @@ -78,6 +78,7 @@ func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { } }() + ui.Message("Waiting for upload token match") log.Printf("Waiting for up to 600 seconds for provider hosted token to match %s", upload.Token) select { @@ -87,7 +88,9 @@ func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + ui.Message(fmt.Sprintf("Upload succesfully verified with token %s", providerCheck.HostedToken)) log.Printf("Box succesfully verified %s == %s", upload.Token, providerCheck.HostedToken) + return multistep.ActionContinue case <-time.After(600 * time.Second): state.Put("error", fmt.Errorf("Timeout while waiting to for upload to verify token '%s'", upload.Token)) diff --git a/website/source/docs/post-processors/vagrant-cloud.html.markdown b/website/source/docs/post-processors/vagrant-cloud.html.markdown index d95ee9343..499234262 100644 --- a/website/source/docs/post-processors/vagrant-cloud.html.markdown +++ b/website/source/docs/post-processors/vagrant-cloud.html.markdown @@ -66,6 +66,9 @@ access to on Vagrant Cloud, as well as authentication and version information. on Vagrant Cloud, making it active. You can manually release the version via the API or Web UI. Defaults to false. +* `vagrant_cloud_url` (string) - Override the base URL for Vagrant Cloud. This +is useful if you're using Vagrant Private Cloud in your own network. Defaults +to `https://vagrantcloud.com/api/v1` ## Use with Vagrant Post-Processor From e528cd7c8cf765321e1395fe815f93bb5c0b4388 Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Wed, 25 Jun 2014 12:49:44 -0400 Subject: [PATCH 14/14] post-processor/vagrant-cloud: fix artifact --- post-processor/vagrant-cloud/artifact.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/post-processor/vagrant-cloud/artifact.go b/post-processor/vagrant-cloud/artifact.go index cebef8696..775bc2998 100644 --- a/post-processor/vagrant-cloud/artifact.go +++ b/post-processor/vagrant-cloud/artifact.go @@ -2,19 +2,18 @@ package vagrantcloud import ( "fmt" - "os" ) const BuilderId = "pearkes.post-processor.vagrant-cloud" type Artifact struct { - Path string + Tag string Provider string } -func NewArtifact(provider, path string) *Artifact { +func NewArtifact(provider, tag string) *Artifact { return &Artifact{ - Path: path, + Tag: tag, Provider: provider, } } @@ -24,7 +23,7 @@ func (*Artifact) BuilderId() string { } func (a *Artifact) Files() []string { - return []string{a.Path} + return nil } func (a *Artifact) Id() string { @@ -32,9 +31,9 @@ func (a *Artifact) Id() string { } func (a *Artifact) String() string { - return fmt.Sprintf("'%s' provider box: %s", a.Provider, a.Path) + return fmt.Sprintf("'%s': %s", a.Provider, a.Tag) } func (a *Artifact) Destroy() error { - return os.Remove(a.Path) + return nil }