mirror of
https://github.com/hashicorp/packer.git
synced 2026-04-21 14:20:40 -04:00
* Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at https://hashi.co/license-faq, and details of the license at www.hashicorp.com/bsl. * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
358 lines
10 KiB
Go
358 lines
10 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/hashicorp/packer-plugin-sdk/template"
|
|
kvflag "github.com/hashicorp/packer/command/flag-kv"
|
|
"github.com/hashicorp/packer/hcl2template"
|
|
"github.com/hashicorp/packer/helper/wrappedstreams"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/version"
|
|
)
|
|
|
|
// FlagSetFlags is an enum to define what flags are present in the
|
|
// default FlagSet returned by Meta.FlagSet
|
|
type FlagSetFlags uint
|
|
|
|
const (
|
|
FlagSetNone FlagSetFlags = 0
|
|
FlagSetBuildFilter FlagSetFlags = 1 << iota
|
|
FlagSetVars
|
|
)
|
|
|
|
// Meta contains the meta-options and functionality that nearly every
|
|
// Packer command inherits.
|
|
type Meta struct {
|
|
CoreConfig *packer.CoreConfig
|
|
Ui packersdk.Ui
|
|
Version string
|
|
}
|
|
|
|
// Core returns the core for the given template given the configured
|
|
// CoreConfig and user variables on this Meta.
|
|
func (m *Meta) Core(tpl *template.Template, cla *MetaArgs) (*packer.Core, error) {
|
|
// Copy the config so we don't modify it
|
|
config := *m.CoreConfig
|
|
config.Template = tpl
|
|
|
|
fj := &kvflag.FlagJSON{}
|
|
// First populate fj with contents from var files
|
|
for _, file := range cla.VarFiles {
|
|
err := fj.Set(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Now read fj values back into flagvars and set as config.Variables. Only
|
|
// add to flagVars if the key doesn't already exist, because flagVars comes
|
|
// from the command line and should not be overridden by variable files.
|
|
if cla.Vars == nil {
|
|
cla.Vars = map[string]string{}
|
|
}
|
|
for k, v := range *fj {
|
|
if _, exists := cla.Vars[k]; !exists {
|
|
cla.Vars[k] = v
|
|
}
|
|
}
|
|
config.Variables = cla.Vars
|
|
|
|
core := packer.NewCore(&config)
|
|
return core, nil
|
|
}
|
|
|
|
// FlagSet returns a FlagSet with the common flags that every
|
|
// command implements. The exact behavior of FlagSet can be configured
|
|
// using the flags as the second parameter, for example to disable
|
|
// build settings on the commands that don't handle builds.
|
|
func (m *Meta) FlagSet(n string, _ FlagSetFlags) *flag.FlagSet {
|
|
f := flag.NewFlagSet(n, flag.ContinueOnError)
|
|
|
|
// Create an io.Writer that writes to our Ui properly for errors.
|
|
// This is kind of a hack, but it does the job. Basically: create
|
|
// a pipe, use a scanner to break it into lines, and output each line
|
|
// to the UI. Do this forever.
|
|
errR, errW := io.Pipe()
|
|
errScanner := bufio.NewScanner(errR)
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
m.Ui.Error(errScanner.Text())
|
|
}
|
|
}()
|
|
f.SetOutput(errW)
|
|
|
|
return f
|
|
}
|
|
|
|
// ValidateFlags should be called after parsing flags to validate the
|
|
// given flags
|
|
func (m *Meta) ValidateFlags() error {
|
|
// TODO
|
|
return nil
|
|
}
|
|
|
|
// StdinPiped returns true if the input is piped.
|
|
func (m *Meta) StdinPiped() bool {
|
|
fi, err := wrappedstreams.Stdin().Stat()
|
|
if err != nil {
|
|
// If there is an error, let's just say its not piped
|
|
return false
|
|
}
|
|
|
|
return fi.Mode()&os.ModeNamedPipe != 0
|
|
}
|
|
|
|
func (m *Meta) GetConfig(cla *MetaArgs) (packer.Handler, int) {
|
|
cfgType, err := cla.GetConfigType()
|
|
if err != nil {
|
|
m.Ui.Error(fmt.Sprintf("%q: %s", cla.Path, err))
|
|
return nil, 1
|
|
}
|
|
|
|
switch cfgType {
|
|
case ConfigTypeHCL2:
|
|
packer.CheckpointReporter.SetTemplateType(packer.HCL2Template)
|
|
// TODO(azr): allow to pass a slice of files here.
|
|
return m.GetConfigFromHCL(cla)
|
|
default:
|
|
packer.CheckpointReporter.SetTemplateType(packer.JSONTemplate)
|
|
// TODO: uncomment once we've polished HCL a bit more.
|
|
// c.Ui.Say(`Legacy JSON Configuration Will Be Used.
|
|
// The template will be parsed in the legacy configuration style. This style
|
|
// will continue to work but users are encouraged to move to the new style.
|
|
// See: https://packer.io/guides/hcl
|
|
// `)
|
|
return m.GetConfigFromJSON(cla)
|
|
}
|
|
}
|
|
|
|
func (m *Meta) GetConfigFromHCL(cla *MetaArgs) (*hcl2template.PackerConfig, int) {
|
|
parser := &hcl2template.Parser{
|
|
CorePackerVersion: version.SemVer,
|
|
CorePackerVersionString: version.FormattedVersion(),
|
|
Parser: hclparse.NewParser(),
|
|
PluginConfig: m.CoreConfig.Components.PluginConfig,
|
|
ValidationOptions: hcl2template.ValidationOptions{
|
|
WarnOnUndeclaredVar: cla.WarnOnUndeclaredVar,
|
|
},
|
|
}
|
|
cfg, diags := parser.Parse(cla.Path, cla.VarFiles, cla.Vars)
|
|
return cfg, writeDiags(m.Ui, parser.Files(), diags)
|
|
}
|
|
|
|
func (m *Meta) GetConfigFromJSON(cla *MetaArgs) (packer.Handler, int) {
|
|
// Parse the template
|
|
var tpl *template.Template
|
|
var err error
|
|
if cla.Path == "" {
|
|
// here cla validation passed so this means we want a default builder
|
|
// and we probably are in the console command
|
|
tpl, err = template.Parse(TiniestBuilder)
|
|
} else {
|
|
tpl, err = template.ParseFile(cla.Path)
|
|
}
|
|
|
|
if err != nil {
|
|
m.Ui.Error(fmt.Sprintf("Failed to parse file as legacy JSON template: "+
|
|
"if you are using an HCL template, check your file extensions; they "+
|
|
"should be either *.pkr.hcl or *.pkr.json; see the docs for more "+
|
|
"details: https://www.packer.io/docs/templates/hcl_templates. \n"+
|
|
"Original error: %s", err))
|
|
return nil, 1
|
|
}
|
|
|
|
// Get the core
|
|
core, err := m.Core(tpl, cla)
|
|
ret := 0
|
|
if err != nil {
|
|
m.Ui.Error(err.Error())
|
|
ret = 1
|
|
}
|
|
return core, ret
|
|
}
|
|
|
|
func (m *Meta) DetectBundledPlugins(handler packer.Handler) hcl.Diagnostics {
|
|
var plugins []string
|
|
|
|
switch h := handler.(type) {
|
|
case *packer.Core:
|
|
plugins = m.detectBundledPluginsJSON(h)
|
|
case *hcl2template.PackerConfig:
|
|
plugins = m.detectBundledPluginsHCL2(handler.(*hcl2template.PackerConfig))
|
|
}
|
|
|
|
if len(plugins) == 0 {
|
|
return nil
|
|
}
|
|
|
|
packer.CheckpointReporter.SetBundledUsage()
|
|
|
|
buf := &strings.Builder{}
|
|
buf.WriteString("This template relies on the use of plugins bundled into the Packer binary.\n")
|
|
buf.WriteString("The practice of bundling external plugins into Packer will be removed in an upcoming version.\n\n")
|
|
switch h := handler.(type) {
|
|
case *packer.Core:
|
|
buf.WriteString("To remove this warning and ensure builds keep working you can install these external plugins with the 'packer plugins install' command\n\n")
|
|
|
|
for _, plugin := range plugins {
|
|
fmt.Fprintf(buf, "* packer plugins install %s\n", plugin)
|
|
}
|
|
|
|
buf.WriteString("\nAlternatively, if you upgrade your templates to HCL2, you can use 'packer init' with a 'required_plugins' block to automatically install external plugins.\n\n")
|
|
fmt.Fprintf(buf, "You can try HCL2 by running 'packer hcl2_upgrade %s'", h.Template.Path)
|
|
case *hcl2template.PackerConfig:
|
|
buf.WriteString("To remove this warning, add the following section to your template:\n")
|
|
buf.WriteString(m.fixRequiredPlugins(h))
|
|
buf.WriteString("\nThen run 'packer init' to manage installation of the plugins")
|
|
}
|
|
|
|
return hcl.Diagnostics{
|
|
&hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Bundled plugins used",
|
|
Detail: buf.String(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (m *Meta) detectBundledPluginsJSON(core *packer.Core) []string {
|
|
bundledPlugins := map[string]struct{}{}
|
|
|
|
tmpl := core.Template
|
|
if tmpl == nil {
|
|
panic("No template parsed. This is a Packer bug which should be reported, please open an issue on the project's issue tracker.")
|
|
}
|
|
|
|
for _, b := range tmpl.Builders {
|
|
builderType := fmt.Sprintf("packer-builder-%s", b.Type)
|
|
if bundledStatus[builderType] {
|
|
bundledPlugins[builderType] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, p := range tmpl.Provisioners {
|
|
provisionerType := fmt.Sprintf("packer-provisioner-%s", p.Type)
|
|
if bundledStatus[provisionerType] {
|
|
bundledPlugins[provisionerType] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, pps := range tmpl.PostProcessors {
|
|
for _, pp := range pps {
|
|
postProcessorType := fmt.Sprintf("packer-post-processor-%s", pp.Type)
|
|
if bundledStatus[postProcessorType] {
|
|
bundledPlugins[postProcessorType] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return compileBundledPluginList(bundledPlugins)
|
|
}
|
|
|
|
var knownPluginPrefixes = map[string]string{
|
|
"amazon": "github.com/hashicorp/amazon",
|
|
"ansible": "github.com/hashicorp/ansible",
|
|
"azure": "github.com/hashicorp/azure",
|
|
"docker": "github.com/hashicorp/docker",
|
|
"googlecompute": "github.com/hashicorp/googlecompute",
|
|
"qemu": "github.com/hashicorp/qemu",
|
|
"vagrant": "github.com/hashicorp/vagrant",
|
|
"vmware": "github.com/hashicorp/vmware",
|
|
"vsphere": "github.com/hashicorp/vsphere",
|
|
}
|
|
|
|
func (m *Meta) fixRequiredPlugins(config *hcl2template.PackerConfig) string {
|
|
plugins := map[string]struct{}{}
|
|
|
|
for _, b := range config.Builds {
|
|
for _, b := range b.Sources {
|
|
for prefix, plugin := range knownPluginPrefixes {
|
|
if strings.HasPrefix(b.Type, prefix) {
|
|
plugins[plugin] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, p := range b.ProvisionerBlocks {
|
|
for prefix, plugin := range knownPluginPrefixes {
|
|
if strings.HasPrefix(p.PType, prefix) {
|
|
plugins[plugin] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, pps := range b.PostProcessorsLists {
|
|
for _, pp := range pps {
|
|
for prefix, plugin := range knownPluginPrefixes {
|
|
if strings.HasPrefix(pp.PType, prefix) {
|
|
plugins[plugin] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, ds := range config.Datasources {
|
|
for prefix, plugin := range knownPluginPrefixes {
|
|
if strings.HasPrefix(ds.Type, prefix) {
|
|
plugins[plugin] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
retPlugins := make([]string, 0, len(plugins))
|
|
for plugin := range plugins {
|
|
retPlugins = append(retPlugins, plugin)
|
|
}
|
|
|
|
return generateRequiredPluginsBlock(retPlugins)
|
|
}
|
|
|
|
func (m *Meta) detectBundledPluginsHCL2(config *hcl2template.PackerConfig) []string {
|
|
bundledPlugins := map[string]struct{}{}
|
|
|
|
for _, b := range config.Builds {
|
|
for _, src := range b.Sources {
|
|
builderType := fmt.Sprintf("packer-builder-%s", src.Type)
|
|
if bundledStatus[builderType] {
|
|
bundledPlugins[builderType] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, p := range b.ProvisionerBlocks {
|
|
provisionerType := fmt.Sprintf("packer-provisioner-%s", p.PType)
|
|
if bundledStatus[provisionerType] {
|
|
bundledPlugins[provisionerType] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, pps := range b.PostProcessorsLists {
|
|
for _, pp := range pps {
|
|
postProcessorType := fmt.Sprintf("packer-post-processor-%s", pp.PType)
|
|
if bundledStatus[postProcessorType] {
|
|
bundledPlugins[postProcessorType] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, ds := range config.Datasources {
|
|
dsType := fmt.Sprintf("packer-datasource-%s", ds.Type)
|
|
if bundledStatus[dsType] {
|
|
bundledPlugins[dsType] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return compileBundledPluginList(bundledPlugins)
|
|
}
|