github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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 if len(errs.Errors) > 0 { 98 return errs 99 } 100 101 return nil 102 } 103 104 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 105 // Only accepts input from the vagrant post-processor 106 if artifact.BuilderId() != "mitchellh.post-processor.vagrant" { 107 return nil, false, fmt.Errorf( 108 "Unknown artifact type, requires box from vagrant post-processor: %s", artifact.BuilderId()) 109 } 110 111 // We assume that there is only one .box file to upload 112 if !strings.HasSuffix(artifact.Files()[0], ".box") { 113 return nil, false, fmt.Errorf( 114 "Unknown files in artifact from vagrant post-processor: %s", artifact.Files()) 115 } 116 117 if p.warnAtlasToken { 118 ui.Message("Warning: Using Vagrant Cloud token found in ATLAS_TOKEN. Please make sure it is correct, or set VAGRANT_CLOUD_TOKEN") 119 } 120 121 // create the HTTP client 122 p.client = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken) 123 124 // The name of the provider for vagrant cloud, and vagrant 125 providerName := providerFromBuilderName(artifact.Id()) 126 127 p.config.ctx.Data = &boxDownloadUrlTemplate{ 128 ArtifactId: artifact.Id(), 129 Provider: providerName, 130 } 131 boxDownloadUrl, err := interpolate.Render(p.config.BoxDownloadUrl, &p.config.ctx) 132 if err != nil { 133 return nil, false, fmt.Errorf("Error processing box_download_url: %s", err) 134 } 135 136 // Set up the state 137 state := new(multistep.BasicStateBag) 138 state.Put("config", p.config) 139 state.Put("client", p.client) 140 state.Put("artifact", artifact) 141 state.Put("artifactFilePath", artifact.Files()[0]) 142 state.Put("ui", ui) 143 state.Put("providerName", providerName) 144 state.Put("boxDownloadUrl", boxDownloadUrl) 145 146 // Build the steps 147 steps := []multistep.Step{} 148 if p.config.BoxDownloadUrl == "" { 149 steps = []multistep.Step{ 150 new(stepVerifyBox), 151 new(stepCreateVersion), 152 new(stepCreateProvider), 153 new(stepPrepareUpload), 154 new(stepUpload), 155 new(stepReleaseVersion), 156 } 157 } else { 158 steps = []multistep.Step{ 159 new(stepVerifyBox), 160 new(stepCreateVersion), 161 new(stepCreateProvider), 162 new(stepReleaseVersion), 163 } 164 } 165 166 // Run the steps 167 p.runner = common.NewRunner(steps, p.config.PackerConfig, ui) 168 p.runner.Run(state) 169 170 // If there was an error, return that 171 if rawErr, ok := state.GetOk("error"); ok { 172 return nil, false, rawErr.(error) 173 } 174 175 return NewArtifact(providerName, p.config.Tag), true, nil 176 } 177 178 // Runs a cleanup if the post processor fails to upload 179 func (p *PostProcessor) Cancel() { 180 if p.runner != nil { 181 log.Println("Cancelling the step runner...") 182 p.runner.Cancel() 183 } 184 } 185 186 // converts a packer builder name to the corresponding vagrant 187 // provider 188 func providerFromBuilderName(name string) string { 189 switch name { 190 case "aws": 191 return "aws" 192 case "scaleway": 193 return "scaleway" 194 case "digitalocean": 195 return "digitalocean" 196 case "virtualbox": 197 return "virtualbox" 198 case "vmware": 199 return "vmware_desktop" 200 case "parallels": 201 return "parallels" 202 default: 203 return name 204 } 205 }