packer-cn/packer/core.go

707 lines
19 KiB
Go
Raw Normal View History

2015-05-23 17:48:07 -04:00
package packer
import (
command/validate: Add support for HCL2 configuration files * Update validate command to use FixConfig for checking against known fixers * Update validation command flag docs * Add ConfigFixer method to PackerHandler Interface * Implement ConfigFixer interface in PackerConfig * Remove all stdout messaging (i.e calls to c.Ui.Say) in the validate command. The command will only display hcl.Diagnotic messaging when there is an error or warning. HCL2 Configs ``` ⇶ packer validate docker_centos_shell_provisioner.pkr.hcl ``` JSON Configs ``` ⇶ packer validate vmware-iso_ubuntu_minimal/vmware-iso_ubuntu_minimal.json Error: Failed to prepare build: "vmware-iso" 1 error occurred: * Deprecated configuration key: 'iso_checksum_type'. Please call `packer fix` against your template to update your template to be compatable with the current version of Packer. Visit https://www.packer.io/docs/commands/fix/ for more detail. Warning: Fixable configuration found. You may need to run `packer fix` to get your build to run correctly. See debug log for more information. map[string]interface{}{ "builders": []interface{}{ map[string]interface{}{ ... // 3 identical entries "guest_os_type": string("ubuntu-64"), "http_directory": string("http"), - "iso_checksum": string("946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"), + "iso_checksum": string("sha256:946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"), - "iso_checksum_type": string("sha256"), "iso_url": string("http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04.1-server-amd64.iso"), "shutdown_command": string("echo 'vagrant' | sudo -S shutdown -P now"), ... // 4 identical entries }, }, } ```
2020-06-02 14:58:33 -04:00
"encoding/json"
2015-05-23 17:48:07 -04:00
"fmt"
"log"
"regexp"
2015-05-23 19:12:32 -04:00
"sort"
"strings"
2019-03-13 17:59:05 -04:00
ttmp "text/template"
2015-05-23 17:48:07 -04:00
command/validate: Add support for HCL2 configuration files * Update validate command to use FixConfig for checking against known fixers * Update validation command flag docs * Add ConfigFixer method to PackerHandler Interface * Implement ConfigFixer interface in PackerConfig * Remove all stdout messaging (i.e calls to c.Ui.Say) in the validate command. The command will only display hcl.Diagnotic messaging when there is an error or warning. HCL2 Configs ``` ⇶ packer validate docker_centos_shell_provisioner.pkr.hcl ``` JSON Configs ``` ⇶ packer validate vmware-iso_ubuntu_minimal/vmware-iso_ubuntu_minimal.json Error: Failed to prepare build: "vmware-iso" 1 error occurred: * Deprecated configuration key: 'iso_checksum_type'. Please call `packer fix` against your template to update your template to be compatable with the current version of Packer. Visit https://www.packer.io/docs/commands/fix/ for more detail. Warning: Fixable configuration found. You may need to run `packer fix` to get your build to run correctly. See debug log for more information. map[string]interface{}{ "builders": []interface{}{ map[string]interface{}{ ... // 3 identical entries "guest_os_type": string("ubuntu-64"), "http_directory": string("http"), - "iso_checksum": string("946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"), + "iso_checksum": string("sha256:946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"), - "iso_checksum_type": string("sha256"), "iso_url": string("http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04.1-server-amd64.iso"), "shutdown_command": string("echo 'vagrant' | sudo -S shutdown -P now"), ... // 4 identical entries }, }, } ```
2020-06-02 14:58:33 -04:00
"github.com/google/go-cmp/cmp"
2019-01-10 09:27:02 -05:00
multierror "github.com/hashicorp/go-multierror"
version "github.com/hashicorp/go-version"
2020-05-08 10:41:47 -04:00
"github.com/hashicorp/hcl/v2"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/template"
"github.com/hashicorp/packer/template/interpolate"
2015-05-23 17:48:07 -04:00
)
// Core is the main executor of Packer. If Packer is being used as a
// library, this is the struct you'll want to instantiate to get anything done.
type Core struct {
2015-05-29 18:41:52 -04:00
Template *template.Template
2015-05-23 17:48:07 -04:00
components ComponentFinder
variables map[string]string
2015-05-23 19:12:32 -04:00
builds map[string]*template.Builder
version string
2018-08-01 14:20:52 -04:00
secrets []string
2019-01-10 09:27:02 -05:00
except []string
only []string
2015-05-23 17:48:07 -04:00
}
// CoreConfig is the structure for initializing a new Core. Once a CoreConfig
// is used to initialize a Core, it shouldn't be re-used or modified again.
type CoreConfig struct {
Components ComponentFinder
Template *template.Template
Variables map[string]string
SensitiveVariables []string
Version string
2019-01-10 09:27:02 -05:00
// These are set by command-line flags
Except []string
Only []string
2015-05-23 17:48:07 -04:00
}
2015-05-25 20:29:10 -04:00
// The function type used to lookup Builder implementations.
type BuilderFunc func(name string) (Builder, error)
// The function type used to lookup Hook implementations.
type HookFunc func(name string) (Hook, error)
// The function type used to lookup PostProcessor implementations.
type PostProcessorFunc func(name string) (PostProcessor, error)
// The function type used to lookup Provisioner implementations.
type ProvisionerFunc func(name string) (Provisioner, error)
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
type BasicStore interface {
Has(name string) bool
List() (names []string)
}
type BuilderStore interface {
BasicStore
Start(name string) (Builder, error)
}
type ProvisionerStore interface {
BasicStore
Start(name string) (Provisioner, error)
}
type PostProcessorStore interface {
BasicStore
Start(name string) (PostProcessor, error)
}
2015-05-25 20:29:10 -04:00
// ComponentFinder is a struct that contains the various function
// pointers necessary to look up components of Packer such as builders,
// commands, etc.
type ComponentFinder struct {
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
Hook HookFunc
// For HCL2
BuilderStore BuilderStore
ProvisionerStore ProvisionerStore
PostProcessorStore PostProcessorStore
2015-05-25 20:29:10 -04:00
}
2015-05-23 17:48:07 -04:00
// NewCore creates a new Core.
func NewCore(c *CoreConfig) (*Core, error) {
result := &Core{
2015-05-29 18:41:52 -04:00
Template: c.Template,
2015-05-23 17:48:07 -04:00
components: c.Components,
variables: c.Variables,
version: c.Version,
2019-01-10 09:27:02 -05:00
only: c.Only,
except: c.Except,
}
if err := result.validate(); err != nil {
return nil, err
}
if err := result.init(); err != nil {
return nil, err
}
for _, secret := range result.secrets {
LogSecretFilter.Set(secret)
}
2018-03-13 23:22:32 -04:00
// Go through and interpolate all the build names. We should be able
2015-05-29 17:29:32 -04:00
// to do this at this point with the variables.
result.builds = make(map[string]*template.Builder)
for _, b := range c.Template.Builders {
2015-05-29 18:35:55 -04:00
v, err := interpolate.Render(b.Name, result.Context())
2015-05-29 17:29:32 -04:00
if err != nil {
return nil, fmt.Errorf(
"Error interpolating builder '%s': %s",
b.Name, err)
}
result.builds[v] = b
}
return result, nil
2015-05-23 17:48:07 -04:00
}
2015-05-23 19:12:32 -04:00
// BuildNames returns the builds that are available in this configured core.
func (c *Core) BuildNames(only, except []string) []string {
sort.Strings(only)
sort.Strings(except)
2020-06-09 09:23:29 -04:00
c.except = except
c.only = only
2015-05-23 19:12:32 -04:00
r := make([]string, 0, len(c.builds))
2016-11-01 17:08:04 -04:00
for n := range c.builds {
onlyPos := sort.SearchStrings(only, n)
foundInOnly := onlyPos < len(only) && only[onlyPos] == n
if len(only) > 0 && !foundInOnly {
continue
}
if pos := sort.SearchStrings(except, n); pos < len(except) && except[pos] == n {
continue
}
2015-05-23 19:12:32 -04:00
r = append(r, n)
}
sort.Strings(r)
return r
}
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName string) (CoreBuildProvisioner, error) {
// Get the provisioner
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
cbp := CoreBuildProvisioner{}
provisioner, err := c.components.ProvisionerStore.Start(rawP.Type)
if err != nil {
return cbp, fmt.Errorf(
"error initializing provisioner '%s': %s",
rawP.Type, err)
}
if provisioner == nil {
return cbp, fmt.Errorf(
"provisioner type not found: %s", rawP.Type)
}
// Get the configuration
config := make([]interface{}, 1, 2)
config[0] = rawP.Config
if rawP.Override != nil {
if override, ok := rawP.Override[rawName]; ok {
config = append(config, override)
}
}
// If we're pausing, we wrap the provisioner in a special pauser.
if rawP.PauseBefore != 0 {
provisioner = &PausedProvisioner{
PauseBefore: rawP.PauseBefore,
Provisioner: provisioner,
}
} else if rawP.Timeout != 0 {
provisioner = &TimeoutProvisioner{
Timeout: rawP.Timeout,
Provisioner: provisioner,
}
}
if rawP.MaxRetries != 0 {
provisioner = &RetriedProvisioner{
MaxRetries: rawP.MaxRetries,
Provisioner: provisioner,
}
}
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
cbp = CoreBuildProvisioner{
PType: rawP.Type,
Provisioner: provisioner,
config: config,
}
return cbp, nil
}
// This is used for json templates to launch the build plugins.
// They will be prepared via b.Prepare() later.
2020-05-08 10:41:47 -04:00
func (c *Core) GetBuilds(opts GetBuildsOptions) ([]Build, hcl.Diagnostics) {
buildNames := c.BuildNames(opts.Only, opts.Except)
builds := []Build{}
diags := hcl.Diagnostics{}
for _, n := range buildNames {
b, err := c.Build(n)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to initialize build %q", n),
Detail: err.Error(),
})
continue
}
// Now that build plugin has been launched, call Prepare()
log.Printf("Preparing build: %s", b.Name())
b.SetDebug(opts.Debug)
b.SetForce(opts.Force)
b.SetOnError(opts.OnError)
warnings, err := b.Prepare()
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to prepare build: %q", n),
Detail: err.Error(),
})
continue
}
// Only append builds to list if the Prepare() is successful.
builds = append(builds, b)
if len(warnings) > 0 {
for _, warning := range warnings {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("Warning when preparing build: %q", n),
Detail: warning,
})
}
}
2020-05-08 10:41:47 -04:00
}
return builds, diags
}
2015-05-23 18:08:50 -04:00
// Build returns the Build object for the given name.
func (c *Core) Build(n string) (Build, error) {
// Setup the builder
configBuilder, ok := c.builds[n]
2015-05-23 18:08:50 -04:00
if !ok {
return nil, fmt.Errorf("no such build found: %s", n)
}
// BuilderStore = config.Builders, gathered in loadConfig() in main.go
// For reference, the builtin BuilderStore is generated in
// packer/config.go in the Discover() func.
// the Start command launches the builder plugin of the given type without
// calling Prepare() or passing any build-specific details.
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
builder, err := c.components.BuilderStore.Start(configBuilder.Type)
2015-05-23 18:08:50 -04:00
if err != nil {
return nil, fmt.Errorf(
"error initializing builder '%s': %s",
configBuilder.Type, err)
}
if builder == nil {
return nil, fmt.Errorf(
"builder type not found: %s", configBuilder.Type)
}
2015-05-26 12:14:29 -04:00
// rawName is the uninterpolated name that we use for various lookups
rawName := configBuilder.Name
// Setup the provisioners for this build
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
provisioners := make([]CoreBuildProvisioner, 0, len(c.Template.Provisioners))
2015-05-29 18:41:52 -04:00
for _, rawP := range c.Template.Provisioners {
2015-05-26 12:14:29 -04:00
// If we're skipping this, then ignore it
2019-01-10 09:27:02 -05:00
if rawP.OnlyExcept.Skip(rawName) {
2015-05-26 12:14:29 -04:00
continue
}
cbp, err := c.generateCoreBuildProvisioner(rawP, rawName)
2015-05-26 12:14:29 -04:00
if err != nil {
return nil, err
2015-05-26 12:14:29 -04:00
}
provisioners = append(provisioners, cbp)
}
2015-05-26 12:14:29 -04:00
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
var cleanupProvisioner CoreBuildProvisioner
if c.Template.CleanupProvisioner != nil {
// This is a special instantiation of the shell-local provisioner that
// is only run on error at end of provisioning step before other step
// cleanup occurs.
cleanupProvisioner, err = c.generateCoreBuildProvisioner(c.Template.CleanupProvisioner, rawName)
if err != nil {
return nil, err
2015-05-26 12:14:29 -04:00
}
}
2015-05-23 18:08:50 -04:00
2015-05-26 12:28:59 -04:00
// Setup the post-processors
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
postProcessors := make([][]CoreBuildPostProcessor, 0, len(c.Template.PostProcessors))
2015-05-29 18:41:52 -04:00
for _, rawPs := range c.Template.PostProcessors {
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
current := make([]CoreBuildPostProcessor, 0, len(rawPs))
2015-05-26 12:28:59 -04:00
for _, rawP := range rawPs {
if rawP.Skip(rawName) {
continue
}
// -except skips post-processor & build
foundExcept := false
for _, except := range c.except {
if except != "" && except == rawP.Name {
foundExcept = true
}
2019-01-10 09:27:02 -05:00
}
if foundExcept {
2020-06-09 11:35:53 -04:00
break
2015-05-26 12:28:59 -04:00
}
// Get the post-processor
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
postProcessor, err := c.components.PostProcessorStore.Start(rawP.Type)
2015-05-26 12:28:59 -04:00
if err != nil {
return nil, fmt.Errorf(
"error initializing post-processor '%s': %s",
rawP.Type, err)
}
if postProcessor == nil {
return nil, fmt.Errorf(
"post-processor type not found: %s", rawP.Type)
}
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
current = append(current, CoreBuildPostProcessor{
PostProcessor: postProcessor,
PType: rawP.Type,
PName: rawP.Name,
2015-05-26 12:28:59 -04:00
config: rawP.Config,
keepInputArtifact: rawP.KeepInputArtifact,
})
}
// If we have no post-processors in this chain, just continue.
if len(current) == 0 {
continue
}
postProcessors = append(postProcessors, current)
}
// TODO hooks one day
// Return a structure that contains the plugins, their types, variables, and
// the raw builder config loaded from the json template
build using HCL2 (#8423) 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 } ```
2019-12-17 05:25:56 -05:00
return &CoreBuild{
Type: n,
Builder: builder,
BuilderConfig: configBuilder.Config,
BuilderType: configBuilder.Type,
PostProcessors: postProcessors,
Provisioners: provisioners,
CleanupProvisioner: cleanupProvisioner,
TemplatePath: c.Template.Path,
Variables: c.variables,
2015-05-23 18:08:50 -04:00
}, nil
}
2015-05-29 18:35:55 -04:00
// Context returns an interpolation context.
func (c *Core) Context() *interpolate.Context {
return &interpolate.Context{
2015-05-29 18:41:52 -04:00
TemplatePath: c.Template.Path,
2015-05-29 18:35:55 -04:00
UserVariables: c.variables,
}
}
2020-06-05 11:23:54 -04:00
var ConsoleHelp = strings.TrimSpace(`
Packer console JSON Mode.
The Packer console allows you to experiment with Packer interpolations.
You may access variables in the Packer config you called the console with.
Type in the interpolation to test and hit <enter> to see the result.
"variables" will dump all available variables and their values.
"{{timestamp}}" will output the timestamp, for example "1559855090".
To exit the console, type "exit" and hit <enter>, or use Control-C.
/!\ If you would like to start console in hcl2 mode without a config you can
use the --config-type=hcl2 option.
`)
func (c *Core) EvaluateExpression(line string) (string, bool, hcl.Diagnostics) {
switch {
case line == "":
return "", false, nil
case line == "exit":
return "", true, nil
case line == "help":
return ConsoleHelp, false, nil
case line == "variables":
varsstring := "\n"
for k, v := range c.Context().UserVariables {
varsstring += fmt.Sprintf("%s: %+v,\n", k, v)
}
return varsstring, false, nil
default:
ctx := c.Context()
rendered, err := interpolate.Render(line, ctx)
var diags hcl.Diagnostics
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: "Interpolation error",
Detail: err.Error(),
})
}
return rendered, false, diags
}
}
command/validate: Add support for HCL2 configuration files * Update validate command to use FixConfig for checking against known fixers * Update validation command flag docs * Add ConfigFixer method to PackerHandler Interface * Implement ConfigFixer interface in PackerConfig * Remove all stdout messaging (i.e calls to c.Ui.Say) in the validate command. The command will only display hcl.Diagnotic messaging when there is an error or warning. HCL2 Configs ``` ⇶ packer validate docker_centos_shell_provisioner.pkr.hcl ``` JSON Configs ``` ⇶ packer validate vmware-iso_ubuntu_minimal/vmware-iso_ubuntu_minimal.json Error: Failed to prepare build: "vmware-iso" 1 error occurred: * Deprecated configuration key: 'iso_checksum_type'. Please call `packer fix` against your template to update your template to be compatable with the current version of Packer. Visit https://www.packer.io/docs/commands/fix/ for more detail. Warning: Fixable configuration found. You may need to run `packer fix` to get your build to run correctly. See debug log for more information. map[string]interface{}{ "builders": []interface{}{ map[string]interface{}{ ... // 3 identical entries "guest_os_type": string("ubuntu-64"), "http_directory": string("http"), - "iso_checksum": string("946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"), + "iso_checksum": string("sha256:946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"), - "iso_checksum_type": string("sha256"), "iso_url": string("http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04.1-server-amd64.iso"), "shutdown_command": string("echo 'vagrant' | sudo -S shutdown -P now"), ... // 4 identical entries }, }, } ```
2020-06-02 14:58:33 -04:00
func (c *Core) FixConfig(opts FixConfigOptions) hcl.Diagnostics {
var diags hcl.Diagnostics
// Remove once we have support for the Inplace FixConfigMode
if opts.Mode != Diff {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("FixConfig only supports template diff; FixConfigMode %d not supported", opts.Mode),
})
return diags
}
var rawTemplateData map[string]interface{}
input := make(map[string]interface{})
templateData := make(map[string]interface{})
if err := json.Unmarshal(c.Template.RawContents, &rawTemplateData); err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("unable to read the contents of the JSON configuration file: %s", err),
Detail: err.Error(),
})
return diags
}
// Hold off on Diff for now - need to think about displaying to user.
// delete empty top-level keys since the fixers seem to add them
// willy-nilly
for k := range input {
ml, ok := input[k].([]map[string]interface{})
if !ok {
continue
}
if len(ml) == 0 {
delete(input, k)
}
}
// marshal/unmarshal to make comparable to templateData
var fixedData map[string]interface{}
// Guaranteed to be valid json, so we can ignore errors
j, _ := json.Marshal(input)
if err := json.Unmarshal(j, &fixedData); err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("unable to read the contents of the JSON configuration file: %s", err),
Detail: err.Error(),
})
return diags
}
if diff := cmp.Diff(templateData, fixedData); diff != "" {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Fixable configuration found.\nPlease run `packer fix` to get your build to run correctly.\nSee debug log for more information.",
Detail: diff,
})
}
return diags
}
// validate does a full validation of the template.
2015-05-23 17:48:07 -04:00
//
// This will automatically call template.validate() in addition to doing
2015-05-23 17:48:07 -04:00
// richer semantic checks around variables and so on.
func (c *Core) validate() error {
2015-05-23 17:48:07 -04:00
// First validate the template in general, we can't do anything else
// unless the template itself is valid.
2015-05-29 18:41:52 -04:00
if err := c.Template.Validate(); err != nil {
2015-05-23 17:48:07 -04:00
return err
}
// Validate the minimum version is satisfied
if c.Template.MinVersion != "" {
versionActual, err := version.NewVersion(c.version)
if err != nil {
// This shouldn't happen since we set it via the compiler
panic(err)
}
versionMin, err := version.NewVersion(c.Template.MinVersion)
if err != nil {
return fmt.Errorf(
"min_version is invalid: %s", err)
}
if versionActual.LessThan(versionMin) {
return fmt.Errorf(
"This template requires Packer version %s or higher; using %s",
versionMin,
versionActual)
}
}
2015-05-23 17:48:07 -04:00
// Validate variables are set
var err error
2015-05-29 18:41:52 -04:00
for n, v := range c.Template.Variables {
2015-05-23 17:48:07 -04:00
if v.Required {
if _, ok := c.variables[n]; !ok {
err = multierror.Append(err, fmt.Errorf(
"required variable not set: %s", n))
}
}
}
// TODO: validate all builders exist
// TODO: ^^ provisioner
// TODO: ^^ post-processor
return err
}
func isDoneInterpolating(v string) (bool, error) {
// Check for whether the var contains any more references to `user`, wrapped
// in interpolation syntax.
filter := `{{\s*user\s*\x60.*\x60\s*}}`
matched, err := regexp.MatchString(filter, v)
if err != nil {
return false, fmt.Errorf("Can't tell if interpolation is done: %s", err)
}
if matched {
// not done interpolating; there's still a call to "user" in a template
// engine
return false, nil
}
// No more calls to "user" as a template engine, so we're done.
return true, nil
}
2019-03-08 17:49:47 -05:00
func (c *Core) renderVarsRecursively() (*interpolate.Context, error) {
2015-05-29 18:35:55 -04:00
ctx := c.Context()
2015-05-29 17:29:32 -04:00
ctx.EnableEnv = true
ctx.UserVariables = make(map[string]string)
2019-03-08 17:49:47 -05:00
shouldRetry := true
changed := false
failedInterpolation := ""
// Why this giant loop? User variables can be recursively defined. For
// example:
// "variables": {
// "foo": "bar",
// "baz": "{{user `foo`}}baz",
// "bang": "bang{{user `baz`}}"
// },
// In this situation, we cannot guarantee that we've added "foo" to
// UserVariables before we try to interpolate "baz" the first time. We need
// to have the option to loop back over in order to add the properly
// interpolated "baz" to the UserVariables map.
// Likewise, we'd need to loop up to two times to properly add "bang",
// since that depends on "baz" being set, which depends on "foo" being set.
// We break out of the while loop either if all our variables have been
// interpolated or if after 100 loops we still haven't succeeded in
// interpolating them. Please don't actually nest your variables in 100
// layers of other variables. Please.
// c.Template.Variables is populated by variables defined within the Template
// itself
// c.variables is populated by variables read in from the command line and
// var-files.
// We need to read the keys from both, then loop over all of them to figure
// out the appropriate interpolations.
repeatMap := make(map[string]string)
allKeys := make([]string, 0)
// load in template variables
for k, v := range c.Template.Variables {
repeatMap[k] = v.Default
allKeys = append(allKeys, k)
}
// overwrite template variables with command-line-read variables
for k, v := range c.variables {
repeatMap[k] = v
allKeys = append(allKeys, k)
}
// sort map to force the following loop to be deterministic.
sort.Strings(allKeys)
type keyValue struct {
Key string
Value string
}
sortedMap := make([]keyValue, len(repeatMap))
for _, k := range allKeys {
sortedMap = append(sortedMap, keyValue{k, repeatMap[k]})
}
// Regex to exclude any build function variable or template variable
// from interpolating earlier
// E.g.: {{ .HTTPIP }} won't interpolate now
renderFilter := "{{(\\s|)\\.(.*?)(\\s|)}}"
for i := 0; i < 100; i++ {
shouldRetry = false
changed = false
deleteKeys := []string{}
// First, loop over the variables in the template
for _, kv := range sortedMap {
2019-03-08 17:49:47 -05:00
// Interpolate the default
renderedV, err := interpolate.RenderRegex(kv.Value, ctx, renderFilter)
2019-03-13 17:59:05 -04:00
switch err.(type) {
case nil:
// We only get here if interpolation has succeeded, so something is
// different in this loop than in the last one.
changed = true
c.variables[kv.Key] = renderedV
2019-03-13 17:59:05 -04:00
ctx.UserVariables = c.variables
// Remove fully-interpolated variables from the map, and flag
// variables that still need interpolating for a repeat.
done, err := isDoneInterpolating(kv.Value)
if err != nil {
return ctx, err
}
if done {
deleteKeys = append(deleteKeys, kv.Key)
} else {
shouldRetry = true
}
2019-03-13 17:59:05 -04:00
case ttmp.ExecError:
castError := err.(ttmp.ExecError)
if strings.Contains(castError.Error(), interpolate.ErrVariableNotSetString) {
shouldRetry = true
failedInterpolation = fmt.Sprintf(`"%s": "%s"; error: %s`, kv.Key, kv.Value, err)
} else {
return ctx, err
}
2019-03-13 17:59:05 -04:00
default:
return ctx, fmt.Errorf(
2019-03-13 17:59:05 -04:00
// unexpected interpolation error: abort the run
"error interpolating default value for '%s': %s",
kv.Key, err)
2019-03-13 17:59:05 -04:00
}
2019-03-08 17:49:47 -05:00
}
if !shouldRetry {
2019-03-08 17:49:47 -05:00
break
}
// Clear completed vars from sortedMap before next loop. Do this one
// key at a time because the indices are gonna change ever time you
// delete from the map.
for _, k := range deleteKeys {
for ind, kv := range sortedMap {
if kv.Key == k {
log.Printf("Deleting kv.Value: %s", kv.Value)
sortedMap = append(sortedMap[:ind], sortedMap[ind+1:]...)
break
}
}
}
deleteKeys = []string{}
2019-03-08 17:49:47 -05:00
}
if !changed && shouldRetry {
return ctx, fmt.Errorf("Failed to interpolate %s: Please make sure that "+
2019-03-08 17:49:47 -05:00
"the variable you're referencing has been defined; Packer treats "+
"all variables used to interpolate other user varaibles as "+
"required.", failedInterpolation)
}
return ctx, nil
}
func (c *Core) init() error {
if c.variables == nil {
c.variables = make(map[string]string)
}
// Go through the variables and interpolate the environment and
// user variables
ctx, err := c.renderVarsRecursively()
if err != nil {
return err
}
for _, v := range c.Template.SensitiveVariables {
secret := ctx.UserVariables[v.Key]
c.secrets = append(c.secrets, secret)
}
return nil
}