2019-10-14 10:43:59 -04:00
//go:generate mapstructure-to-hcl2 -type Config
2014-06-16 15:53:37 -04:00
// vagrant_cloud implements the packer.PostProcessor interface and adds a
// post-processor that uploads artifacts from the vagrant post-processor
2019-03-11 23:21:59 -04:00
// and vagrant builder to Vagrant Cloud (vagrantcloud.com) or manages
// self hosted boxes on the Vagrant Cloud
2014-06-16 15:53:37 -04:00
package vagrantcloud
import (
2019-08-20 12:56:24 -04:00
"archive/tar"
"compress/gzip"
2019-03-22 09:56:02 -04:00
"context"
2019-08-21 07:32:42 -04:00
"encoding/json"
2014-06-16 15:53:37 -04:00
"fmt"
2019-08-20 12:56:24 -04:00
"io"
"io/ioutil"
2019-08-21 09:07:55 -04:00
"log"
2017-06-07 13:40:31 -04:00
"os"
2015-05-27 17:56:22 -04:00
"strings"
2019-12-17 05:25:56 -05:00
"github.com/hashicorp/hcl/v2/hcldec"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
2018-01-19 19:18:44 -05:00
"github.com/hashicorp/packer/helper/multistep"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
2014-06-16 15:53:37 -04:00
)
2019-03-11 23:21:59 -04:00
var builtins = map [ string ] string {
"mitchellh.post-processor.vagrant" : "vagrant" ,
2019-08-20 10:44:14 -04:00
"packer.post-processor.artifice" : "artifice" ,
2019-03-11 23:21:59 -04:00
"vagrant" : "vagrant" ,
}
2014-06-23 15:48:51 -04:00
const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1"
2014-06-16 15:53:37 -04:00
type Config struct {
common . PackerConfig ` mapstructure:",squash" `
2014-06-25 10:32:17 -04:00
Tag string ` mapstructure:"box_tag" `
Version string ` mapstructure:"version" `
VersionDescription string ` mapstructure:"version_description" `
NoRelease bool ` mapstructure:"no_release" `
2014-06-16 15:53:37 -04:00
2019-03-05 04:57:11 -05:00
AccessToken string ` mapstructure:"access_token" `
VagrantCloudUrl string ` mapstructure:"vagrant_cloud_url" `
InsecureSkipTLSVerify bool ` mapstructure:"insecure_skip_tls_verify" `
2014-06-16 15:53:37 -04:00
2014-08-01 13:31:10 -04:00
BoxDownloadUrl string ` mapstructure:"box_download_url" `
2015-05-27 17:56:22 -04:00
ctx interpolate . Context
2014-06-16 15:53:37 -04:00
}
type PostProcessor struct {
2019-03-05 04:57:11 -05:00
config Config
client * VagrantCloudClient
runner multistep . Runner
warnAtlasToken bool
insecureSkipTLSVerify bool
2014-06-16 15:53:37 -04:00
}
2019-12-17 05:25:56 -05:00
func ( p * PostProcessor ) ConfigSpec ( ) hcldec . ObjectSpec { return p . config . FlatMapstructure ( ) . HCL2Spec ( ) }
2014-06-16 15:53:37 -04:00
func ( p * PostProcessor ) Configure ( raws ... interface { } ) error {
2015-05-27 17:56:22 -04:00
err := config . Decode ( & p . config , & config . DecodeOpts {
2015-06-22 15:24:27 -04:00
Interpolate : true ,
InterpolateContext : & p . config . ctx ,
2015-05-27 17:56:22 -04:00
InterpolateFilter : & interpolate . RenderFilter {
Exclude : [ ] string {
"box_download_url" ,
} ,
} ,
} , raws ... )
2014-06-16 15:53:37 -04:00
if err != nil {
return err
}
// Default configuration
if p . config . VagrantCloudUrl == "" {
p . config . VagrantCloudUrl = VAGRANT_CLOUD_URL
}
2019-03-05 04:57:11 -05:00
p . insecureSkipTLSVerify = p . config . InsecureSkipTLSVerify == true && p . config . VagrantCloudUrl != VAGRANT_CLOUD_URL
2017-06-07 13:40:31 -04:00
if p . config . AccessToken == "" {
envToken := os . Getenv ( "VAGRANT_CLOUD_TOKEN" )
if envToken == "" {
envToken = os . Getenv ( "ATLAS_TOKEN" )
if envToken != "" {
p . warnAtlasToken = true
}
}
p . config . AccessToken = envToken
}
2014-06-16 15:53:37 -04:00
// Accumulate any errors
errs := new ( packer . MultiError )
2019-08-21 09:07:55 -04:00
// Required configuration
2014-06-16 15:53:37 -04:00
templates := map [ string ] * string {
2019-09-12 06:37:46 -04:00
"box_tag" : & p . config . Tag ,
"version" : & p . config . Version ,
2014-06-16 15:53:37 -04:00
}
for key , ptr := range templates {
if * ptr == "" {
errs = packer . MultiErrorAppend (
errs , fmt . Errorf ( "%s must be set" , key ) )
}
}
2019-09-12 06:37:46 -04:00
if p . config . VagrantCloudUrl == VAGRANT_CLOUD_URL && p . config . AccessToken == "" {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "access_token must be set if vagrant_cloud_url has not been overriden" ) )
}
2019-08-21 09:07:55 -04:00
// Create the HTTP client
2019-03-05 04:57:11 -05:00
p . client , err = VagrantCloudClient { } . New ( p . config . VagrantCloudUrl , p . config . AccessToken , p . insecureSkipTLSVerify )
2018-10-24 09:08:08 -04:00
if err != nil {
errs = packer . MultiErrorAppend (
errs , fmt . Errorf ( "Failed to verify authentication token: %v" , err ) )
}
2014-06-16 15:53:37 -04:00
if len ( errs . Errors ) > 0 {
return errs
}
return nil
}
2019-04-08 13:59:42 -04:00
func ( p * PostProcessor ) PostProcess ( ctx context . Context , ui packer . Ui , artifact packer . Artifact ) ( packer . Artifact , bool , bool , error ) {
2019-03-11 23:21:59 -04:00
if _ , ok := builtins [ artifact . BuilderId ( ) ] ; ! ok {
2019-04-02 19:51:58 -04:00
return nil , false , false , fmt . Errorf (
2020-05-15 16:13:43 -04:00
"Unknown artifact type: this post-processor requires an input artifact from the artifice post-processor, vagrant post-processor, or vagrant builder: %s" , artifact . BuilderId ( ) )
2014-06-16 15:53:37 -04:00
}
2014-06-24 15:58:45 -04:00
// We assume that there is only one .box file to upload
if ! strings . HasSuffix ( artifact . Files ( ) [ 0 ] , ".box" ) {
2019-04-02 19:51:58 -04:00
return nil , false , false , fmt . Errorf (
2019-08-21 09:07:55 -04:00
"Unknown files in artifact, Vagrant box with .box suffix is required as first artifact file: %s" , artifact . Files ( ) )
2014-06-24 15:58:45 -04:00
}
2017-06-07 13:40:31 -04:00
if p . warnAtlasToken {
ui . Message ( "Warning: Using Vagrant Cloud token found in ATLAS_TOKEN. Please make sure it is correct, or set VAGRANT_CLOUD_TOKEN" )
}
2019-08-20 10:44:14 -04:00
// Determine the name of the provider for Vagrant Cloud, and Vagrant
providerName , err := getProvider ( artifact . Id ( ) , artifact . Files ( ) [ 0 ] , builtins [ artifact . BuilderId ( ) ] )
2019-09-25 19:58:26 -04:00
if err != nil {
return nil , false , false , fmt . Errorf ( "error getting provider name: %s" , err )
}
2014-06-24 15:58:45 -04:00
2020-01-30 05:27:58 -05:00
var generatedData map [ interface { } ] interface { }
stateData := artifact . State ( "generated_data" )
if stateData != nil {
// Make sure it's not a nil map so we can assign to it later.
generatedData = stateData . ( map [ interface { } ] interface { } )
}
// If stateData has a nil map generatedData will be nil
// and we need to make sure it's not
if generatedData == nil {
generatedData = make ( map [ interface { } ] interface { } )
2015-05-27 17:56:22 -04:00
}
2020-01-30 05:27:58 -05:00
generatedData [ "ArtifactId" ] = artifact . Id ( )
generatedData [ "Provider" ] = providerName
p . config . ctx . Data = generatedData
2019-08-20 10:44:14 -04:00
2015-05-27 17:56:22 -04:00
boxDownloadUrl , err := interpolate . Render ( p . config . BoxDownloadUrl , & p . config . ctx )
2014-08-01 13:31:10 -04:00
if err != nil {
2019-04-02 19:51:58 -04:00
return nil , false , false , fmt . Errorf ( "Error processing box_download_url: %s" , err )
2014-08-01 13:31:10 -04:00
}
2014-06-23 15:48:51 -04:00
// Set up the state
state := new ( multistep . BasicStateBag )
2019-12-19 14:21:51 -05:00
state . Put ( "config" , & p . config )
2014-06-23 15:48:51 -04:00
state . Put ( "client" , p . client )
state . Put ( "artifact" , artifact )
2014-06-24 15:58:45 -04:00
state . Put ( "artifactFilePath" , artifact . Files ( ) [ 0 ] )
2014-06-23 15:48:51 -04:00
state . Put ( "ui" , ui )
2014-06-24 15:58:45 -04:00
state . Put ( "providerName" , providerName )
2014-08-01 13:31:10 -04:00
state . Put ( "boxDownloadUrl" , boxDownloadUrl )
2014-06-23 15:48:51 -04:00
// Build the steps
2014-08-01 13:31:10 -04:00
steps := [ ] multistep . Step { }
if p . config . BoxDownloadUrl == "" {
steps = [ ] multistep . Step {
new ( stepVerifyBox ) ,
new ( stepCreateVersion ) ,
new ( stepCreateProvider ) ,
new ( stepPrepareUpload ) ,
new ( stepUpload ) ,
new ( stepReleaseVersion ) ,
}
} else {
steps = [ ] multistep . Step {
new ( stepVerifyBox ) ,
new ( stepCreateVersion ) ,
new ( stepCreateProvider ) ,
new ( stepReleaseVersion ) ,
}
2014-06-16 15:53:37 -04:00
}
2014-06-23 15:48:51 -04:00
// Run the steps
2017-08-31 13:43:00 -04:00
p . runner = common . NewRunner ( steps , p . config . PackerConfig , ui )
2019-03-22 09:56:02 -04:00
p . runner . Run ( ctx , state )
2014-06-20 11:20:27 -04:00
2014-06-23 15:48:51 -04:00
// If there was an error, return that
if rawErr , ok := state . GetOk ( "error" ) ; ok {
2019-04-02 19:51:58 -04:00
return nil , false , false , rawErr . ( error )
2014-06-20 11:20:27 -04:00
}
2019-04-02 19:51:58 -04:00
return NewArtifact ( providerName , p . config . Tag ) , true , false , nil
2014-06-16 15:53:37 -04:00
}
2019-08-20 10:44:14 -04:00
func getProvider ( builderName , boxfile , builderId string ) ( providerName string , err error ) {
if builderId == "artifice" {
// The artifice post processor cannot embed any data in the
// supplied artifact so the provider information must be extracted
// from the box file directly
providerName , err = providerFromVagrantBox ( boxfile )
} else {
// For the Vagrant builder and Vagrant post processor the provider can
// be determined from information embedded in the artifact
providerName = providerFromBuilderName ( builderName )
}
return providerName , err
}
// Converts a packer builder name to the corresponding vagrant provider
2014-06-16 15:53:37 -04:00
func providerFromBuilderName ( name string ) string {
switch name {
case "aws" :
return "aws"
2017-04-06 05:19:17 -04:00
case "scaleway" :
return "scaleway"
2014-06-16 15:53:37 -04:00
case "digitalocean" :
return "digitalocean"
case "virtualbox" :
return "virtualbox"
case "vmware" :
return "vmware_desktop"
case "parallels" :
return "parallels"
default :
return name
}
}
2019-08-20 10:44:14 -04:00
// Returns the Vagrant provider the box is intended for use with by
// reading the metadata file packaged inside the box
func providerFromVagrantBox ( boxfile string ) ( providerName string , err error ) {
2019-08-21 09:07:55 -04:00
log . Println ( "Attempting to determine provider from metadata in box file. This may take some time..." )
2019-08-20 11:36:01 -04:00
f , err := os . Open ( boxfile )
if err != nil {
return "" , fmt . Errorf ( "Error attempting to open box file: %s" , err )
}
defer f . Close ( )
2019-08-20 12:56:24 -04:00
// Vagrant boxes are gzipped tar archives
ar , err := gzip . NewReader ( f )
if err != nil {
return "" , fmt . Errorf ( "Error unzipping box archive: %s" , err )
}
tr := tar . NewReader ( ar )
2019-08-21 07:32:42 -04:00
// The metadata.json file in the tar archive contains a 'provider' key
type metadata struct {
ProviderName string ` json:"provider" `
}
md := metadata { }
2019-08-20 12:56:24 -04:00
// Loop through the files in the archive and read the provider
// information from the boxes metadata.json file
for {
hdr , err := tr . Next ( )
if err == io . EOF {
2019-08-21 07:32:42 -04:00
if md . ProviderName == "" {
2019-08-21 05:04:32 -04:00
return "" , fmt . Errorf ( "Error: Provider info was not found in box: %s" , boxfile )
}
2019-08-20 12:56:24 -04:00
break
}
if err != nil {
2019-08-21 04:44:13 -04:00
return "" , fmt . Errorf ( "Error reading header info from box tar archive: %s" , err )
2019-08-20 12:56:24 -04:00
}
if hdr . Name == "metadata.json" {
contents , err := ioutil . ReadAll ( tr )
if err != nil {
return "" , fmt . Errorf ( "Error reading contents of metadata.json file from box file: %s" , err )
}
2019-08-21 07:32:42 -04:00
err = json . Unmarshal ( contents , & md )
if err != nil {
return "" , fmt . Errorf ( "Error parsing metadata.json file: %s" , err )
}
2019-08-21 08:02:42 -04:00
if md . ProviderName == "" {
return "" , fmt . Errorf ( "Error: Could not determine Vagrant provider from box metadata.json file" )
}
2019-08-20 12:56:24 -04:00
break
}
}
2019-08-21 07:32:42 -04:00
return md . ProviderName , nil
2019-08-20 10:44:14 -04:00
}