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 }