packer-cn/packer/core.go

403 lines
11 KiB
Go
Raw Normal View History

2015-05-23 17:48:07 -04:00
package packer
import (
"fmt"
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
2019-01-10 09:27:02 -05:00
multierror "github.com/hashicorp/go-multierror"
version "github.com/hashicorp/go-version"
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)
// 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 {
Builder BuilderFunc
Hook HookFunc
PostProcessor PostProcessorFunc
Provisioner ProvisionerFunc
}
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() []string {
r := make([]string, 0, len(c.builds))
2016-11-01 17:08:04 -04:00
for n := range c.builds {
2015-05-23 19:12:32 -04:00
r = append(r, n)
}
sort.Strings(r)
return r
}
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)
}
builder, err := c.components.Builder(configBuilder.Type)
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
2015-05-29 18:41:52 -04:00
provisioners := make([]coreBuildProvisioner, 0, len(c.Template.Provisioners))
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
}
// Get the provisioner
provisioner, err := c.components.Provisioner(rawP.Type)
if err != nil {
return nil, fmt.Errorf(
"error initializing provisioner '%s': %s",
rawP.Type, err)
}
if provisioner == nil {
return nil, fmt.Errorf(
"provisioner type not found: %s", rawP.Type)
}
// Get the configuration
config := make([]interface{}, 1, 2)
config[0] = rawP.Config
2015-05-26 12:46:04 -04:00
if rawP.Override != nil {
if override, ok := rawP.Override[rawName]; ok {
config = append(config, override)
}
}
2015-05-26 12:14:29 -04:00
// 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,
2015-05-26 12:14:29 -04:00
}
}
provisioners = append(provisioners, coreBuildProvisioner{
pType: rawP.Type,
2015-05-26 12:14:29 -04:00
provisioner: provisioner,
config: config,
})
}
2015-05-23 18:08:50 -04:00
2015-05-26 12:28:59 -04:00
// Setup the post-processors
2015-05-29 18:41:52 -04:00
postProcessors := make([][]coreBuildPostProcessor, 0, len(c.Template.PostProcessors))
for _, rawPs := range c.Template.PostProcessors {
2015-05-26 12:28:59 -04:00
current := make([]coreBuildPostProcessor, 0, len(rawPs))
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 {
continue
2015-05-26 12:28:59 -04:00
}
// Get the post-processor
postProcessor, err := c.components.PostProcessor(rawP.Type)
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)
}
current = append(current, coreBuildPostProcessor{
processor: postProcessor,
processorType: rawP.Type,
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
2015-05-23 18:08:50 -04:00
return &coreBuild{
2015-05-26 12:28:59 -04:00
name: n,
builder: builder,
builderConfig: configBuilder.Config,
builderType: configBuilder.Type,
postProcessors: postProcessors,
provisioners: provisioners,
2015-05-29 18:41:52 -04:00
templatePath: c.Template.Path,
2015-05-26 12:28:59 -04:00
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,
}
}
// 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 (c *Core) init() error {
if c.variables == nil {
c.variables = make(map[string]string)
}
2019-03-08 17:49:47 -05:00
// Go through the variables and interpolate the environment and
// user variables
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.
allVariables := make(map[string]string)
// load in template variables
for k, v := range c.Template.Variables {
allVariables[k] = v.Default
}
// overwrite template variables with command-line-read variables
for k, v := range c.variables {
allVariables[k] = v
}
for i := 0; i < 100; i++ {
shouldRetry = false
// First, loop over the variables in the template
for k, v := range allVariables {
2019-03-08 17:49:47 -05:00
// Interpolate the default
renderedV, err := interpolate.Render(v, ctx)
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[k] = renderedV
2019-03-13 17:59:05 -04:00
ctx.UserVariables = c.variables
case ttmp.ExecError:
castError := err.(ttmp.ExecError)
if strings.Contains(castError.Error(), interpolate.ErrVariableNotSetString) {
shouldRetry = true
failedInterpolation = fmt.Sprintf(`"%s": "%s"; error: %s`, k, v, err)
} else {
return err
}
2019-03-13 17:59:05 -04:00
default:
return fmt.Errorf(
// unexpected interpolation error: abort the run
"error interpolating default value for '%s': %s",
k, err)
}
2019-03-08 17:49:47 -05:00
}
if !shouldRetry {
2019-03-08 17:49:47 -05:00
break
}
2019-03-08 17:49:47 -05:00
}
if (changed == false) && (shouldRetry == true) {
return fmt.Errorf("Failed to interpolate %s: Please make sure that "+
"the variable you're referencing has been defined; Packer treats "+
"all variables used to interpolate other user varaibles as "+
"required.", failedInterpolation)
}
for _, v := range c.Template.SensitiveVariables {
secret := ctx.UserVariables[v.Key]
c.secrets = append(c.secrets, secret)
}
return nil
}