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 }