packer-cn/packer/provisioner.go

239 lines
7.3 KiB
Go
Raw Normal View History

2013-05-22 16:25:03 -04:00
package packer
import (
"context"
"fmt"
"log"
"sync"
2013-12-21 00:36:41 -05:00
"time"
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
"github.com/hashicorp/hcl/v2/hcldec"
2020-12-17 16:29:25 -05:00
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
)
// A HookedProvisioner represents a provisioner and information describing it
type HookedProvisioner struct {
Provisioner packersdk.Provisioner
Config interface{}
TypeName string
}
2013-05-24 00:13:18 -04:00
// A Hook implementation that runs the given provisioners.
type ProvisionHook struct {
// The provisioners to run as part of the hook. These should already
// be prepared (by calling Prepare) at some earlier stage.
Provisioners []*HookedProvisioner
2013-05-24 00:13:18 -04:00
}
// BuilderDataCommonKeys is the list of common keys that all builder will
// return
var BuilderDataCommonKeys = []string{
"ID",
2020-07-07 05:52:08 -04:00
// The following correspond to communicator-agnostic functions that are }
// part of the SSH and WinRM communicator implementations. These functions
// are not part of the communicator interface, but are stored on the
// Communicator Config and return the appropriate values rather than
// depending on the actual communicator config values. E.g "Password"
// reprosents either WinRMPassword or SSHPassword, which makes this more
// useful if a template contains multiple builds.
"Host",
"Port",
"User",
"Password",
"ConnType",
"PackerRunUUID",
"PackerHTTPPort",
"PackerHTTPIP",
"PackerHTTPAddr",
"SSHPublicKey",
"SSHPrivateKey",
"WinRMPassword",
}
// Provisioners interpolate most of their fields in the prepare stage; this
// placeholder map helps keep fields that are only generated at build time from
// accidentally being interpolated into empty strings at prepare time.
// This helper function generates the most basic placeholder data which should
// be accessible to the provisioners. It is used to initialize provisioners, to
// force validation using the `generated` template function. In the future,
// custom generated data could be passed into provisioners from builders to
// enable specialized builder-specific (but still validated!!) access to builder
// data.
2020-07-06 05:18:02 -04:00
func BasicPlaceholderData() map[string]string {
placeholderData := map[string]string{}
for _, key := range BuilderDataCommonKeys {
placeholderData[key] = fmt.Sprintf("Build_%s. "+packerbuilderdata.PlaceholderMsg, key)
}
// Backwards-compatability: WinRM Password can get through without forcing
// the generated func validation.
placeholderData["WinRMPassword"] = "{{.WinRMPassword}}"
return placeholderData
}
func CastDataToMap(data interface{}) map[string]interface{} {
if interMap, ok := data.(map[string]interface{}); ok {
// null and file builder sometimes don't use a communicator and
// therefore don't go through RPC
return interMap
}
// Provisioners expect a map[string]interface{} in their data field, but
// it gets converted into a map[interface]interface on the way over the
// RPC. Check that data can be cast into such a form, and cast it.
cast := make(map[string]interface{})
interMap, ok := data.(map[interface{}]interface{})
if !ok {
log.Printf("Unable to read map[string]interface out of data."+
"Using empty interface: %#v", data)
} else {
for key, val := range interMap {
keyString, ok := key.(string)
if ok {
cast[keyString] = val
} else {
log.Printf("Error casting generated data key to a string.")
}
}
}
return cast
}
2013-05-24 00:13:18 -04:00
// Runs the provisioners in order.
func (h *ProvisionHook) Run(ctx context.Context, name string, ui packersdk.Ui, comm packersdk.Communicator, data interface{}) error {
// Shortcut
if len(h.Provisioners) == 0 {
return nil
}
if comm == nil {
return fmt.Errorf(
"No communicator found for provisioners! This is usually because the\n" +
"`communicator` config was set to \"none\". If you have any provisioners\n" +
"then a communicator is required. Please fix this to continue.")
}
2017-11-03 02:31:32 -04:00
for _, p := range h.Provisioners {
ts := CheckpointReporter.AddSpan(p.TypeName, "provisioner", p.Config)
2017-11-03 02:31:32 -04:00
cast := CastDataToMap(data)
err := p.Provisioner.Provision(ctx, ui, comm, cast)
2017-11-03 02:31:32 -04:00
ts.End(err)
if err != nil {
return err
}
2013-05-24 00:13:18 -04:00
}
return nil
2013-05-24 00:13:18 -04:00
}
2013-08-30 20:03:55 -04:00
2013-12-21 00:36:41 -05:00
// PausedProvisioner is a Provisioner implementation that pauses before
// the provisioner is actually run.
type PausedProvisioner struct {
PauseBefore time.Duration
Provisioner packersdk.Provisioner
2013-12-21 00:36:41 -05: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
func (p *PausedProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() }
func (p *PausedProvisioner) FlatConfig() interface{} { return p.FlatConfig() }
2013-12-21 00:36:41 -05:00
func (p *PausedProvisioner) Prepare(raws ...interface{}) error {
return p.Provisioner.Prepare(raws...)
}
func (p *PausedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{}) error {
2013-12-21 00:36:41 -05:00
// Use a select to determine if we get cancelled during the wait
ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore))
2013-12-21 00:36:41 -05:00
select {
case <-time.After(p.PauseBefore):
case <-ctx.Done():
return ctx.Err()
2013-12-21 00:36:41 -05:00
}
return p.Provisioner.Provision(ctx, ui, comm, generatedData)
2013-12-21 00:36:41 -05:00
}
// RetriedProvisioner is a Provisioner implementation that retries
// the provisioner whenever there's an error.
type RetriedProvisioner struct {
MaxRetries int
Provisioner packersdk.Provisioner
}
func (r *RetriedProvisioner) ConfigSpec() hcldec.ObjectSpec { return r.ConfigSpec() }
func (r *RetriedProvisioner) FlatConfig() interface{} { return r.FlatConfig() }
func (r *RetriedProvisioner) Prepare(raws ...interface{}) error {
return r.Provisioner.Prepare(raws...)
}
func (r *RetriedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{}) error {
if ctx.Err() != nil { // context was cancelled
return ctx.Err()
}
err := r.Provisioner.Provision(ctx, ui, comm, generatedData)
if err == nil {
return nil
}
leftTries := r.MaxRetries
for ; leftTries > 0; leftTries-- {
if ctx.Err() != nil { // context was cancelled
return ctx.Err()
}
ui.Say(fmt.Sprintf("Provisioner failed with %q, retrying with %d trie(s) left", err, leftTries))
err := r.Provisioner.Provision(ctx, ui, comm, generatedData)
if err == nil {
return nil
}
}
ui.Say("retry limit reached.")
return err
}
// DebuggedProvisioner is a Provisioner implementation that waits until a key
// press before the provisioner is actually run.
type DebuggedProvisioner struct {
Provisioner packersdk.Provisioner
cancelCh chan struct{}
doneCh chan struct{}
lock sync.Mutex
}
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 (p *DebuggedProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() }
func (p *DebuggedProvisioner) FlatConfig() interface{} { return p.FlatConfig() }
func (p *DebuggedProvisioner) Prepare(raws ...interface{}) error {
return p.Provisioner.Prepare(raws...)
}
func (p *DebuggedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{}) error {
// Use a select to determine if we get cancelled during the wait
message := "Pausing before the next provisioner . Press enter to continue."
result := make(chan string, 1)
go func() {
line, err := ui.Ask(message)
if err != nil {
log.Printf("Error asking for input: %s", err)
}
result <- line
}()
select {
case <-result:
case <-ctx.Done():
return ctx.Err()
}
return p.Provisioner.Provision(ctx, ui, comm, generatedData)
}