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