github.com/hashicorp/packer@v1.14.3/command/meta.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package command
     5  
     6  import (
     7  	"bufio"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  
    13  	"github.com/hashicorp/hcl/v2/hclparse"
    14  	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
    15  	"github.com/hashicorp/packer-plugin-sdk/template"
    16  	kvflag "github.com/hashicorp/packer/command/flag-kv"
    17  	"github.com/hashicorp/packer/hcl2template"
    18  	"github.com/hashicorp/packer/helper/wrappedstreams"
    19  	"github.com/hashicorp/packer/packer"
    20  	"github.com/hashicorp/packer/version"
    21  )
    22  
    23  // Meta contains the meta-options and functionality that nearly every
    24  // Packer command inherits.
    25  type Meta struct {
    26  	CoreConfig *packer.CoreConfig
    27  	Ui         packersdk.Ui
    28  	Version    string
    29  }
    30  
    31  // Core returns the core for the given template given the configured
    32  // CoreConfig and user variables on this Meta.
    33  func (m *Meta) Core(tpl *template.Template, cla *MetaArgs) (*packer.Core, error) {
    34  	// Copy the config so we don't modify it
    35  	config := *m.CoreConfig
    36  	config.Template = tpl
    37  
    38  	fj := &kvflag.FlagJSON{}
    39  	// First populate fj with contents from var files
    40  	for _, file := range cla.VarFiles {
    41  		err := fj.Set(file)
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  	}
    46  	// Now read fj values back into flagvars and set as config.Variables. Only
    47  	// add to flagVars if the key doesn't already exist, because flagVars comes
    48  	// from the command line and should not be overridden by variable files.
    49  	if cla.Vars == nil {
    50  		cla.Vars = map[string]string{}
    51  	}
    52  	for k, v := range *fj {
    53  		if _, exists := cla.Vars[k]; !exists {
    54  			cla.Vars[k] = v
    55  		}
    56  	}
    57  	config.Variables = cla.Vars
    58  
    59  	core := packer.NewCore(&config)
    60  	return core, nil
    61  }
    62  
    63  // FlagSet returns a FlagSet with Packer SDK Ui support built-in
    64  func (m *Meta) FlagSet(n string) *flag.FlagSet {
    65  	f := flag.NewFlagSet(n, flag.ContinueOnError)
    66  
    67  	// Create an io.Writer that writes to our Ui properly for errors.
    68  	// This is kind of a hack, but it does the job. Basically: create
    69  	// a pipe, use a scanner to break it into lines, and output each line
    70  	// to the UI. Do this forever.
    71  	errR, errW := io.Pipe()
    72  	errScanner := bufio.NewScanner(errR)
    73  	go func() {
    74  		for errScanner.Scan() {
    75  			m.Ui.Error(errScanner.Text())
    76  		}
    77  	}()
    78  	f.SetOutput(errW)
    79  
    80  	return f
    81  }
    82  
    83  // ValidateFlags should be called after parsing flags to validate the
    84  // given flags
    85  func (m *Meta) ValidateFlags() error {
    86  	// TODO
    87  	return nil
    88  }
    89  
    90  // StdinPiped returns true if the input is piped.
    91  func (m *Meta) StdinPiped() bool {
    92  	fi, err := wrappedstreams.Stdin().Stat()
    93  	if err != nil {
    94  		// If there is an error, let's just say its not piped
    95  		return false
    96  	}
    97  
    98  	return fi.Mode()&os.ModeNamedPipe != 0
    99  }
   100  
   101  func (m *Meta) GetConfig(cla *MetaArgs) (packer.Handler, int) {
   102  	cfgType, err := cla.GetConfigType()
   103  	if err != nil {
   104  		m.Ui.Error(fmt.Sprintf("%q: %s", cla.Path, err))
   105  		return nil, 1
   106  	}
   107  
   108  	switch cfgType {
   109  	case ConfigTypeHCL2:
   110  		packer.CheckpointReporter.SetTemplateType(packer.HCL2Template)
   111  		// TODO(azr): allow to pass a slice of files here.
   112  		return m.GetConfigFromHCL(cla)
   113  	default:
   114  		packer.CheckpointReporter.SetTemplateType(packer.JSONTemplate)
   115  		// TODO: uncomment once we've polished HCL a bit more.
   116  		// c.Ui.Say(`Legacy JSON Configuration Will Be Used.
   117  		// The template will be parsed in the legacy configuration style. This style
   118  		// will continue to work but users are encouraged to move to the new style.
   119  		// See: https://packer.io/guides/hcl
   120  		// `)
   121  		return m.GetConfigFromJSON(cla)
   122  	}
   123  }
   124  
   125  func (m *Meta) GetConfigFromHCL(cla *MetaArgs) (*hcl2template.PackerConfig, int) {
   126  	parser := &hcl2template.Parser{
   127  		CorePackerVersion:       version.SemVer,
   128  		CorePackerVersionString: version.FormattedVersion(),
   129  		Parser:                  hclparse.NewParser(),
   130  		PluginConfig:            m.CoreConfig.Components.PluginConfig,
   131  		ValidationOptions: hcl2template.ValidationOptions{
   132  			WarnOnUndeclaredVar: cla.WarnOnUndeclaredVar,
   133  		},
   134  	}
   135  	cfg, diags := parser.Parse(cla.Path, cla.VarFiles, cla.Vars)
   136  	return cfg, writeDiags(m.Ui, parser.Files(), diags)
   137  }
   138  
   139  func (m *Meta) GetConfigFromJSON(cla *MetaArgs) (packer.Handler, int) {
   140  	// Parse the template
   141  	var tpl *template.Template
   142  	var err error
   143  	if cla.Path == "" {
   144  		// here cla validation passed so this means we want a default builder
   145  		// and we probably are in the console command
   146  		tpl, err = template.Parse(TiniestBuilder)
   147  	} else {
   148  		tpl, err = template.ParseFile(cla.Path)
   149  	}
   150  
   151  	if err != nil {
   152  		m.Ui.Error(fmt.Sprintf("Failed to parse file as legacy JSON template: "+
   153  			"if you are using an HCL template, check your file extensions; they "+
   154  			"should be either *.pkr.hcl or *.pkr.json; see the docs for more "+
   155  			"details: https://www.packer.io/docs/templates/hcl_templates. \n"+
   156  			"Original error: %s", err))
   157  		return nil, 1
   158  	}
   159  
   160  	// Get the core
   161  	core, err := m.Core(tpl, cla)
   162  	ret := 0
   163  	if err != nil {
   164  		m.Ui.Error(err.Error())
   165  		ret = 1
   166  	}
   167  	return core, ret
   168  }