Files
Packer-Cn/packer/provisioner.go
T

247 lines
8.1 KiB
Go
Raw Normal View History

2013-05-22 13:25:03 -07:00
package packer
2013-08-30 23:39:29 -07:00
import (
"context"
"fmt"
2017-03-12 23:46:35 +01:00
"log"
2013-08-30 23:39:29 -07:00
"sync"
2013-12-20 21:36:41 -08:00
"time"
2019-12-17 11:25:56 +01:00
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/helper/common"
2013-08-30 23:39:29 -07:00
)
2013-05-22 13:25:03 -07:00
// A provisioner is responsible for installing and configuring software
// on a machine prior to building the actual image.
type Provisioner interface {
2019-12-17 11:25:56 +01:00
HCL2Speccer
2013-06-06 17:02:02 -07:00
// Prepare is called with a set of configurations to setup the
// internal state of the provisioner. The multiple configurations
// should be merged in some sane way.
2013-06-06 17:07:42 -07:00
Prepare(...interface{}) error
2013-05-22 13:25:03 -07:00
// Provision is called to actually provision the machine. A context is
// given for cancellation, a UI is given to communicate with the user, and
// a communicator is given that is guaranteed to be connected to some
// machine so that provisioning can be done.
Provision(context.Context, Ui, Communicator, map[string]interface{}) error
2013-05-22 13:25:03 -07:00
}
2013-05-23 21:13:18 -07:00
2017-11-04 13:06:36 -07:00
// A HookedProvisioner represents a provisioner and information describing it
type HookedProvisioner struct {
Provisioner Provisioner
Config interface{}
TypeName string
}
2013-05-23 21:13:18 -07: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.
2017-11-04 13:06:36 -07:00
Provisioners []*HookedProvisioner
2013-05-23 21:13:18 -07:00
}
// 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.
func BasicPlaceholderData() map[string]string {
placeholderData := map[string]string{}
msg := "Build_%s. " + common.PlaceholderMsg
placeholderData["ID"] = fmt.Sprintf(msg, "ID")
// 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.
placeholderData["Host"] = fmt.Sprintf(msg, "Host")
placeholderData["Port"] = fmt.Sprintf(msg, "Port")
placeholderData["User"] = fmt.Sprintf(msg, "User")
placeholderData["Password"] = fmt.Sprintf(msg, "Password")
placeholderData["ConnType"] = fmt.Sprintf(msg, "Type")
placeholderData["PackerRunUUID"] = fmt.Sprintf(msg, "PackerRunUUID")
placeholderData["PackerHTTPPort"] = fmt.Sprintf(msg, "PackerHTTPPort")
placeholderData["PackerHTTPIP"] = fmt.Sprintf(msg, "PackerHTTPIP")
2020-01-27 16:49:31 +01:00
placeholderData["PackerHTTPAddr"] = fmt.Sprintf(msg, "PackerHTTPAddr")
placeholderData["SSHPublicKey"] = fmt.Sprintf(msg, "SSHPublicKey")
placeholderData["SSHPrivateKey"] = fmt.Sprintf(msg, "SSHPrivateKey")
// 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-23 21:13:18 -07:00
// Runs the provisioners in order.
func (h *ProvisionHook) Run(ctx context.Context, name string, ui Ui, comm 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-02 23:31:32 -07:00
for _, p := range h.Provisioners {
2017-11-04 13:06:36 -07:00
ts := CheckpointReporter.AddSpan(p.TypeName, "provisioner", p.Config)
2017-11-02 23:31:32 -07:00
cast := CastDataToMap(data)
err := p.Provisioner.Provision(ctx, ui, comm, cast)
2017-11-02 23:31:32 -07:00
2017-05-04 16:29:21 -07:00
ts.End(err)
if err != nil {
return err
}
2013-05-23 21:13:18 -07:00
}
return nil
2013-05-23 21:13:18 -07:00
}
2013-08-30 17:03:55 -07:00
2013-12-20 21:36:41 -08:00
// PausedProvisioner is a Provisioner implementation that pauses before
// the provisioner is actually run.
type PausedProvisioner struct {
PauseBefore time.Duration
Provisioner Provisioner
}
2019-12-17 11:25:56 +01:00
func (p *PausedProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() }
func (p *PausedProvisioner) FlatConfig() interface{} { return p.FlatConfig() }
2013-12-20 21:36:41 -08:00
func (p *PausedProvisioner) Prepare(raws ...interface{}) error {
return p.Provisioner.Prepare(raws...)
}
func (p *PausedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error {
2013-12-20 21:36:41 -08: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-20 21:36:41 -08:00
select {
case <-time.After(p.PauseBefore):
case <-ctx.Done():
return ctx.Err()
2013-12-20 21:36:41 -08:00
}
return p.Provisioner.Provision(ctx, ui, comm, generatedData)
2013-12-20 21:36:41 -08:00
}
2017-03-12 23:46:35 +01:00
// RetriedProvisioner is a Provisioner implementation that retries
// the provisioner whenever there's an error.
type RetriedProvisioner struct {
MaxRetries int
Provisioner 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 Ui, comm 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.
2017-03-12 23:46:35 +01:00
type DebuggedProvisioner struct {
Provisioner Provisioner
cancelCh chan struct{}
doneCh chan struct{}
lock sync.Mutex
}
2019-12-17 11:25:56 +01:00
func (p *DebuggedProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() }
func (p *DebuggedProvisioner) FlatConfig() interface{} { return p.FlatConfig() }
2017-03-12 23:46:35 +01:00
func (p *DebuggedProvisioner) Prepare(raws ...interface{}) error {
return p.Provisioner.Prepare(raws...)
}
func (p *DebuggedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error {
2017-03-12 23:46:35 +01:00
// 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()
2017-03-12 23:46:35 +01:00
}
return p.Provisioner.Provision(ctx, ui, comm, generatedData)
2017-03-12 23:46:35 +01:00
}