Commit 3bdd9ccb authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #1289 from mitchellh/vagrant-cloud-post-processor

Vagrant Cloud Post-Processor
parents cbfb6ec3 e528cd7c
...@@ -47,7 +47,8 @@ const defaultConfig = ` ...@@ -47,7 +47,8 @@ const defaultConfig = `
"vagrant": "packer-post-processor-vagrant", "vagrant": "packer-post-processor-vagrant",
"vsphere": "packer-post-processor-vsphere", "vsphere": "packer-post-processor-vsphere",
"docker-push": "packer-post-processor-docker-push", "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": { "provisioners": {
......
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()
}
package vagrantcloud
import (
"fmt"
)
const BuilderId = "pearkes.post-processor.vagrant-cloud"
type Artifact struct {
Tag string
Provider string
}
func NewArtifact(provider, tag string) *Artifact {
return &Artifact{
Tag: tag,
Provider: provider,
}
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
return ""
}
func (a *Artifact) String() string {
return fmt.Sprintf("'%s': %s", a.Provider, a.Tag)
}
func (a *Artifact) Destroy() error {
return nil
}
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")
}
}
package vagrantcloud
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
type VagrantCloudClient struct {
// The http client for communicating
client *http.Client
// The base URL of the API
BaseURL string
// Access token
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{
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) 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)
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
}
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 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)
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())
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(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)
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
}
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
}
// 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/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"log"
"strings"
)
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"`
VersionDescription string `mapstructure:"version_description"`
NoRelease bool `mapstructure:"no_release"`
AccessToken string `mapstructure:"access_token"`
VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"`
tpl *packer.ConfigTemplate
}
type PostProcessor struct {
config Config
client *VagrantCloudClient
runner multistep.Runner
}
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) {
// 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())
}
// 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{
new(stepVerifyBox),
new(stepCreateVersion),
new(stepCreateProvider),
new(stepPrepareUpload),
new(stepUpload),
new(stepVerifyUpload),
new(stepReleaseVersion),
}
// Run the steps
if p.config.PackerDebug {
p.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
p.runner = &multistep.BasicRunner{Steps: steps}
}
p.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, false, rawErr.(error)
}
return NewArtifact(providerName, p.config.Tag), true, nil
}
// Runs a cleanup if the post processor fails to upload
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
// 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
}
}
package vagrantcloud
import (
"bytes"
"github.com/mitchellh/packer/packer"
"testing"
)
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 TestPostProcessor_Configure_Good(t *testing.T) {
var p PostProcessor
if err := p.Configure(testGoodConfig()); err != nil {
t.Fatalf("err: %s", err)
}
}
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 {
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")
}
}
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) {
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
}
_, 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
}
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)
// No need for resp from the cleanup DELETE
_, err := client.Delete(path)
if err != nil {
ui.Error(fmt.Sprintf("Error destroying provider: %s", err))
}
}
package vagrantcloud
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type Version struct {
Version string `json:"version"`
Description string `json:"description,omitempty"`
Number uint `json:"number,omitempty"`
}
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)
ui.Say(fmt.Sprintf("Creating version: %s", config.Version))
if hasVersion, v := box.HasVersion(config.Version); hasVersion {
ui.Message(fmt.Sprintf("Version exists, skipping creation"))
state.Put("version", v)
return multistep.ActionContinue
}
path := fmt.Sprintf("box/%s/versions", box.Tag)
version := &Version{Version: config.Version, Description: config.VersionDescription}
// Wrap the version in a version object for the API
wrapper := make(map[string]interface{})
wrapper["version"] = version
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 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) {
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
}
_, 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
}
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)
if err != nil {
ui.Error(fmt.Sprintf("Error destroying version: %s", err))
}
}
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
}
ui.Message(fmt.Sprintf("Box upload prepared with token %s", upload.Token))
// Save the upload details to the state
state.Put("upload", upload)
return multistep.ActionContinue
}
func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) {
// No cleanup
}
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)
config := state.Get("config").(Config)
ui.Say(fmt.Sprintf("Releasing version: %s", version.Version))
if config.NoRelease {
ui.Message("Not releasing version due to configuration")
return multistep.ActionContinue
}
path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Number)
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
}
ui.Message(fmt.Sprintf("Version successfully released and available"))
return multistep.ActionContinue
}
func (s *stepReleaseVersion) Cleanup(state multistep.StateBag) {
// No cleanup
}
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))
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) {
state.Put("error", fmt.Errorf("Error uploading Box: %s", err))
return multistep.ActionHalt
}
ui.Message("Box succesfully uploaded")
return multistep.ActionContinue
}
func (s *stepUpload) Cleanup(state multistep.StateBag) {
// No cleanup
}
package vagrantcloud
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type Box struct {
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 {
}
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 || (resp.StatusCode != 200) {
cloudErrors := &VagrantCloudErrors{}
err = decodeBody(resp, cloudErrors)
state.Put("error", fmt.Errorf("Error retrieving box: %s", cloudErrors.FormatErrors()))
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", config.Tag))
return multistep.ActionHalt
}
ui.Message("Box accessible and matches tag")
// 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
}
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 {
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
}
}
}()
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 {
case err := <-result:
if err != nil {
state.Put("error", err)
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))
return multistep.ActionHalt
}
}
func (s *stepVerifyUpload) Cleanup(state multistep.StateBag) {
// No cleanup
}
...@@ -28,7 +28,7 @@ func (a *Artifact) Files() []string { ...@@ -28,7 +28,7 @@ func (a *Artifact) Files() []string {
} }
func (a *Artifact) Id() string { func (a *Artifact) Id() string {
return "" return a.Provider
} }
func (a *Artifact) String() string { func (a *Artifact) String() string {
......
...@@ -12,3 +12,10 @@ func TestArtifact_ImplementsArtifact(t *testing.T) { ...@@ -12,3 +12,10 @@ func TestArtifact_ImplementsArtifact(t *testing.T) {
t.Fatalf("Artifact should be a Artifact") 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")
}
}
---
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.
* `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
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`}}"
}]
]
}
```
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
<li><a href="/docs/post-processors/docker-import.html">docker-import</a></li> <li><a href="/docs/post-processors/docker-import.html">docker-import</a></li>
<li><a href="/docs/post-processors/docker-push.html">docker-push</a></li> <li><a href="/docs/post-processors/docker-push.html">docker-push</a></li>
<li><a href="/docs/post-processors/vagrant.html">Vagrant</a></li> <li><a href="/docs/post-processors/vagrant.html">Vagrant</a></li>
<li><a href="/docs/post-processors/vagrant-cloud.html">Vagrant Cloud</a></li>
<li><a href="/docs/post-processors/vsphere.html">vSphere</a></li> <li><a href="/docs/post-processors/vsphere.html">vSphere</a></li>
</ul> </ul>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment