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)) + // 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) - box, err := p.client.Box(tag) - - if err != nil { - return nil, false, err + // 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 -}