github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/post-processor/vagrant-cloud/post-processor.go (about)

     1  // vagrant_cloud implements the packer.PostProcessor interface and adds a
     2  // post-processor that uploads artifacts from the vagrant post-processor
     3  // to Vagrant Cloud (vagrantcloud.com) or manages self hosted boxes on the
     4  // Vagrant Cloud
     5  package vagrantcloud
     6  
     7  import (
     8  	"fmt"
     9  	"github.com/mitchellh/multistep"
    10  	"github.com/mitchellh/packer/common"
    11  	"github.com/mitchellh/packer/packer"
    12  	"log"
    13  	"strings"
    14  )
    15  
    16  const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1"
    17  
    18  type Config struct {
    19  	common.PackerConfig `mapstructure:",squash"`
    20  
    21  	Tag                string `mapstructure:"box_tag"`
    22  	Version            string `mapstructure:"version"`
    23  	VersionDescription string `mapstructure:"version_description"`
    24  	NoRelease          bool   `mapstructure:"no_release"`
    25  
    26  	AccessToken     string `mapstructure:"access_token"`
    27  	VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"`
    28  
    29  	BoxDownloadUrl string `mapstructure:"box_download_url"`
    30  
    31  	tpl *packer.ConfigTemplate
    32  }
    33  
    34  type boxDownloadUrlTemplate struct {
    35  	ArtifactId string
    36  	Provider   string
    37  }
    38  
    39  type PostProcessor struct {
    40  	config Config
    41  	client *VagrantCloudClient
    42  	runner multistep.Runner
    43  }
    44  
    45  func (p *PostProcessor) Configure(raws ...interface{}) error {
    46  	_, err := common.DecodeConfig(&p.config, raws...)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	p.config.tpl, err = packer.NewConfigTemplate()
    52  	if err != nil {
    53  		return err
    54  	}
    55  	p.config.tpl.UserVars = p.config.PackerUserVars
    56  
    57  	// Default configuration
    58  	if p.config.VagrantCloudUrl == "" {
    59  		p.config.VagrantCloudUrl = VAGRANT_CLOUD_URL
    60  	}
    61  
    62  	// Accumulate any errors
    63  	errs := new(packer.MultiError)
    64  
    65  	// required configuration
    66  	templates := map[string]*string{
    67  		"box_tag":      &p.config.Tag,
    68  		"version":      &p.config.Version,
    69  		"access_token": &p.config.AccessToken,
    70  	}
    71  
    72  	for key, ptr := range templates {
    73  		if *ptr == "" {
    74  			errs = packer.MultiErrorAppend(
    75  				errs, fmt.Errorf("%s must be set", key))
    76  		}
    77  	}
    78  
    79  	// Template process
    80  	for key, ptr := range templates {
    81  		*ptr, err = p.config.tpl.Process(*ptr, nil)
    82  		if err != nil {
    83  			errs = packer.MultiErrorAppend(
    84  				errs, fmt.Errorf("Error processing %s: %s", key, err))
    85  		}
    86  	}
    87  
    88  	if len(errs.Errors) > 0 {
    89  		return errs
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
    96  	// Only accepts input from the vagrant post-processor
    97  	if artifact.BuilderId() != "mitchellh.post-processor.vagrant" {
    98  		return nil, false, fmt.Errorf(
    99  			"Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId())
   100  	}
   101  
   102  	// We assume that there is only one .box file to upload
   103  	if !strings.HasSuffix(artifact.Files()[0], ".box") {
   104  		return nil, false, fmt.Errorf(
   105  			"Unknown files in artifact from vagrant post-processor: %s", artifact.Files())
   106  	}
   107  
   108  	// create the HTTP client
   109  	p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken)
   110  
   111  	// The name of the provider for vagrant cloud, and vagrant
   112  	providerName := providerFromBuilderName(artifact.Id())
   113  
   114  	boxDownloadUrl, err := p.config.tpl.Process(p.config.BoxDownloadUrl, &boxDownloadUrlTemplate{
   115  		ArtifactId: artifact.Id(),
   116  		Provider:   providerName,
   117  	})
   118  	if err != nil {
   119  		return nil, false, fmt.Errorf("Error processing box_download_url: %s", err)
   120  	}
   121  
   122  	// Set up the state
   123  	state := new(multistep.BasicStateBag)
   124  	state.Put("config", p.config)
   125  	state.Put("client", p.client)
   126  	state.Put("artifact", artifact)
   127  	state.Put("artifactFilePath", artifact.Files()[0])
   128  	state.Put("ui", ui)
   129  	state.Put("providerName", providerName)
   130  	state.Put("boxDownloadUrl", boxDownloadUrl)
   131  
   132  	// Build the steps
   133  	steps := []multistep.Step{}
   134  	if p.config.BoxDownloadUrl == "" {
   135  		steps = []multistep.Step{
   136  			new(stepVerifyBox),
   137  			new(stepCreateVersion),
   138  			new(stepCreateProvider),
   139  			new(stepPrepareUpload),
   140  			new(stepUpload),
   141  			new(stepVerifyUpload),
   142  			new(stepReleaseVersion),
   143  		}
   144  	} else {
   145  		steps = []multistep.Step{
   146  			new(stepVerifyBox),
   147  			new(stepCreateVersion),
   148  			new(stepCreateProvider),
   149  			new(stepReleaseVersion),
   150  		}
   151  	}
   152  
   153  	// Run the steps
   154  	if p.config.PackerDebug {
   155  		p.runner = &multistep.DebugRunner{
   156  			Steps:   steps,
   157  			PauseFn: common.MultistepDebugFn(ui),
   158  		}
   159  	} else {
   160  		p.runner = &multistep.BasicRunner{Steps: steps}
   161  	}
   162  
   163  	p.runner.Run(state)
   164  
   165  	// If there was an error, return that
   166  	if rawErr, ok := state.GetOk("error"); ok {
   167  		return nil, false, rawErr.(error)
   168  	}
   169  
   170  	return NewArtifact(providerName, p.config.Tag), true, nil
   171  }
   172  
   173  // Runs a cleanup if the post processor fails to upload
   174  func (p *PostProcessor) Cancel() {
   175  	if p.runner != nil {
   176  		log.Println("Cancelling the step runner...")
   177  		p.runner.Cancel()
   178  	}
   179  }
   180  
   181  // converts a packer builder name to the corresponding vagrant
   182  // provider
   183  func providerFromBuilderName(name string) string {
   184  	switch name {
   185  	case "aws":
   186  		return "aws"
   187  	case "digitalocean":
   188  		return "digitalocean"
   189  	case "virtualbox":
   190  		return "virtualbox"
   191  	case "vmware":
   192  		return "vmware_desktop"
   193  	case "parallels":
   194  		return "parallels"
   195  	default:
   196  		return name
   197  	}
   198  }