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