mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-21 23:41:41 -04:00
This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
298 lines
8.4 KiB
Go
298 lines
8.4 KiB
Go
//go:generate mapstructure-to-hcl2 -type Config
|
|
|
|
// vagrant_cloud implements the packer.PostProcessor interface and adds a
|
|
// post-processor that uploads artifacts from the vagrant post-processor
|
|
// and vagrant builder to Vagrant Cloud (vagrantcloud.com) or manages
|
|
// self hosted boxes on the Vagrant Cloud
|
|
package vagrantcloud
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2/hcldec"
|
|
"github.com/hashicorp/packer/common"
|
|
"github.com/hashicorp/packer/helper/config"
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/template/interpolate"
|
|
)
|
|
|
|
var builtins = map[string]string{
|
|
"mitchellh.post-processor.vagrant": "vagrant",
|
|
"packer.post-processor.artifice": "artifice",
|
|
"vagrant": "vagrant",
|
|
}
|
|
|
|
const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1"
|
|
|
|
type Config struct {
|
|
common.PackerConfig `mapstructure:",squash"`
|
|
|
|
Tag string `mapstructure:"box_tag"`
|
|
Version string `mapstructure:"version"`
|
|
VersionDescription string `mapstructure:"version_description"`
|
|
NoRelease bool `mapstructure:"no_release"`
|
|
|
|
AccessToken string `mapstructure:"access_token"`
|
|
VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"`
|
|
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"`
|
|
|
|
BoxDownloadUrl string `mapstructure:"box_download_url"`
|
|
|
|
ctx interpolate.Context
|
|
}
|
|
|
|
type boxDownloadUrlTemplate struct {
|
|
ArtifactId string
|
|
Provider string
|
|
}
|
|
|
|
type PostProcessor struct {
|
|
config Config
|
|
client *VagrantCloudClient
|
|
runner multistep.Runner
|
|
warnAtlasToken bool
|
|
insecureSkipTLSVerify bool
|
|
}
|
|
|
|
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
|
|
|
|
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
|
err := config.Decode(&p.config, &config.DecodeOpts{
|
|
Interpolate: true,
|
|
InterpolateContext: &p.config.ctx,
|
|
InterpolateFilter: &interpolate.RenderFilter{
|
|
Exclude: []string{
|
|
"box_download_url",
|
|
},
|
|
},
|
|
}, raws...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Default configuration
|
|
if p.config.VagrantCloudUrl == "" {
|
|
p.config.VagrantCloudUrl = VAGRANT_CLOUD_URL
|
|
}
|
|
|
|
p.insecureSkipTLSVerify = p.config.InsecureSkipTLSVerify == true && p.config.VagrantCloudUrl != VAGRANT_CLOUD_URL
|
|
|
|
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
|
|
}
|
|
|
|
// Accumulate any errors
|
|
errs := new(packer.MultiError)
|
|
|
|
// Required configuration
|
|
templates := map[string]*string{
|
|
"box_tag": &p.config.Tag,
|
|
"version": &p.config.Version,
|
|
}
|
|
|
|
for key, ptr := range templates {
|
|
if *ptr == "" {
|
|
errs = packer.MultiErrorAppend(
|
|
errs, fmt.Errorf("%s must be set", key))
|
|
}
|
|
}
|
|
|
|
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"))
|
|
}
|
|
|
|
// Create the HTTP client
|
|
p.client, err = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken, p.insecureSkipTLSVerify)
|
|
if err != nil {
|
|
errs = packer.MultiErrorAppend(
|
|
errs, fmt.Errorf("Failed to verify authentication token: %v", err))
|
|
}
|
|
|
|
if len(errs.Errors) > 0 {
|
|
return errs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, bool, error) {
|
|
if _, ok := builtins[artifact.BuilderId()]; !ok {
|
|
return nil, false, false, fmt.Errorf(
|
|
"Unknown artifact type, requires box from vagrant post-processor or vagrant builder: %s", artifact.BuilderId())
|
|
}
|
|
|
|
// We assume that there is only one .box file to upload
|
|
if !strings.HasSuffix(artifact.Files()[0], ".box") {
|
|
return nil, false, false, fmt.Errorf(
|
|
"Unknown files in artifact, Vagrant box with .box suffix is required as first artifact file: %s", artifact.Files())
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
// Determine the name of the provider for Vagrant Cloud, and Vagrant
|
|
providerName, err := getProvider(artifact.Id(), artifact.Files()[0], builtins[artifact.BuilderId()])
|
|
if err != nil {
|
|
return nil, false, false, fmt.Errorf("error getting provider name: %s", err)
|
|
}
|
|
|
|
p.config.ctx.Data = &boxDownloadUrlTemplate{
|
|
ArtifactId: artifact.Id(),
|
|
Provider: providerName,
|
|
}
|
|
|
|
boxDownloadUrl, err := interpolate.Render(p.config.BoxDownloadUrl, &p.config.ctx)
|
|
if err != nil {
|
|
return nil, false, false, fmt.Errorf("Error processing box_download_url: %s", err)
|
|
}
|
|
|
|
// Set up the state
|
|
state := new(multistep.BasicStateBag)
|
|
state.Put("config", p.config)
|
|
state.Put("client", p.client)
|
|
state.Put("artifact", artifact)
|
|
state.Put("artifactFilePath", artifact.Files()[0])
|
|
state.Put("ui", ui)
|
|
state.Put("providerName", providerName)
|
|
state.Put("boxDownloadUrl", boxDownloadUrl)
|
|
|
|
// Build the steps
|
|
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),
|
|
}
|
|
}
|
|
|
|
// Run the steps
|
|
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
|
|
p.runner.Run(ctx, state)
|
|
|
|
// If there was an error, return that
|
|
if rawErr, ok := state.GetOk("error"); ok {
|
|
return nil, false, false, rawErr.(error)
|
|
}
|
|
|
|
return NewArtifact(providerName, p.config.Tag), true, false, nil
|
|
}
|
|
|
|
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
|
|
func providerFromBuilderName(name string) string {
|
|
switch name {
|
|
case "aws":
|
|
return "aws"
|
|
case "scaleway":
|
|
return "scaleway"
|
|
case "digitalocean":
|
|
return "digitalocean"
|
|
case "virtualbox":
|
|
return "virtualbox"
|
|
case "vmware":
|
|
return "vmware_desktop"
|
|
case "parallels":
|
|
return "parallels"
|
|
default:
|
|
return name
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
log.Println("Attempting to determine provider from metadata in box file. This may take some time...")
|
|
|
|
f, err := os.Open(boxfile)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Error attempting to open box file: %s", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
// 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)
|
|
|
|
// The metadata.json file in the tar archive contains a 'provider' key
|
|
type metadata struct {
|
|
ProviderName string `json:"provider"`
|
|
}
|
|
md := metadata{}
|
|
|
|
// 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 {
|
|
if md.ProviderName == "" {
|
|
return "", fmt.Errorf("Error: Provider info was not found in box: %s", boxfile)
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
return "", fmt.Errorf("Error reading header info from box tar archive: %s", err)
|
|
}
|
|
|
|
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)
|
|
}
|
|
err = json.Unmarshal(contents, &md)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Error parsing metadata.json file: %s", err)
|
|
}
|
|
if md.ProviderName == "" {
|
|
return "", fmt.Errorf("Error: Could not determine Vagrant provider from box metadata.json file")
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return md.ProviderName, nil
|
|
}
|