diff --git a/command/cli.go b/command/cli.go index 0a5ebbad4..f268db62a 100644 --- a/command/cli.go +++ b/command/cli.go @@ -121,3 +121,12 @@ type ValidateArgs struct { MetaArgs SyntaxOnly bool } + +func (va *InspectArgs) AddFlagSets(flags *flag.FlagSet) { + va.MetaArgs.AddFlagSets(flags) +} + +// InspectArgs represents a parsed cli line for a `packer inspect` +type InspectArgs struct { + MetaArgs +} diff --git a/command/console_test.go b/command/console_test.go index 0cfc07e21..42b41d47a 100644 --- a/command/console_test.go +++ b/command/console_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_piping(t *testing.T) { +func Test_console(t *testing.T) { tc := []struct { piped string diff --git a/command/inspect.go b/command/inspect.go index 3a368884f..ddfc6fd9a 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -1,12 +1,10 @@ package command import ( - "fmt" - "sort" + "context" "strings" - "github.com/hashicorp/packer/template" - + "github.com/hashicorp/packer/packer" "github.com/posener/complete" ) @@ -15,142 +13,40 @@ type InspectCommand struct { } func (c *InspectCommand) Run(args []string) int { - flags := c.Meta.FlagSet("inspect", FlagSetNone) - flags.Usage = func() { c.Ui.Say(c.Help()) } - if err := flags.Parse(args); err != nil { - return 1 - } + ctx := context.Background() - args = flags.Args() - if len(args) != 1 { - flags.Usage() - return 1 - } - - // Parse the template - tpl, err := template.ParseFile(args[0]) - if err != nil { - c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) - return 1 + cfg, ret := c.ParseArgs(args) + if ret != 0 { + return ret } - // Convenience... - ui := c.Ui - - // Description - if tpl.Description != "" { - ui.Say("Description:\n") - ui.Say(tpl.Description + "\n") - } + return c.RunContext(ctx, cfg) +} - // Variables - if len(tpl.Variables) == 0 { - ui.Say("Variables:\n") - ui.Say(" ") - } else { - requiredHeader := false - for k, v := range tpl.Variables { - for _, sensitive := range tpl.SensitiveVariables { - if ok := strings.Compare(sensitive.Default, v.Default); ok == 0 { - v.Default = "" - } - } - if v.Required { - if !requiredHeader { - requiredHeader = true - ui.Say("Required variables:\n") - } - - ui.Machine("template-variable", k, v.Default, "1") - ui.Say(" " + k) - } - } - - if requiredHeader { - ui.Say("") - } - - ui.Say("Optional variables and their defaults:\n") - keys := make([]string, 0, len(tpl.Variables)) - max := 0 - for k := range tpl.Variables { - keys = append(keys, k) - if len(k) > max { - max = len(k) - } - } - - sort.Strings(keys) - - for _, k := range keys { - v := tpl.Variables[k] - if v.Required { - continue - } - for _, sensitive := range tpl.SensitiveVariables { - if ok := strings.Compare(sensitive.Default, v.Default); ok == 0 { - v.Default = "" - } - } - - padding := strings.Repeat(" ", max-len(k)) - output := fmt.Sprintf(" %s%s = %s", k, padding, v.Default) - - ui.Machine("template-variable", k, v.Default, "0") - ui.Say(output) - } +func (c *InspectCommand) ParseArgs(args []string) (*InspectArgs, int) { + var cfg InspectArgs + flags := c.Meta.FlagSet("inspect", FlagSetVars) + flags.Usage = func() { c.Ui.Say(c.Help()) } + cfg.AddFlagSets(flags) + if err := flags.Parse(args); err != nil { + return &cfg, 1 } - ui.Say("") - - // Builders - ui.Say("Builders:\n") - if len(tpl.Builders) == 0 { - ui.Say(" ") - } else { - keys := make([]string, 0, len(tpl.Builders)) - max := 0 - for k := range tpl.Builders { - keys = append(keys, k) - if len(k) > max { - max = len(k) - } - } - - sort.Strings(keys) - - for _, k := range keys { - v := tpl.Builders[k] - padding := strings.Repeat(" ", max-len(k)) - output := fmt.Sprintf(" %s%s", k, padding) - if v.Name != v.Type { - output = fmt.Sprintf("%s (%s)", output, v.Type) - } - - ui.Machine("template-builder", k, v.Type) - ui.Say(output) - - } + args = flags.Args() + if len(args) == 1 { + cfg.Path = args[0] } + return &cfg, 0 +} - ui.Say("") - - // Provisioners - ui.Say("Provisioners:\n") - if len(tpl.Provisioners) == 0 { - ui.Say(" ") - } else { - for _, v := range tpl.Provisioners { - ui.Machine("template-provisioner", v.Type) - ui.Say(fmt.Sprintf(" %s", v.Type)) - } +func (c *InspectCommand) RunContext(ctx context.Context, cla *InspectArgs) int { + packerStarter, ret := c.GetConfig(&cla.MetaArgs) + if ret != 0 { + return ret } - - ui.Say("\nNote: If your build names contain user variables or template\n" + - "functions such as 'timestamp', these are processed at build time,\n" + - "and therefore only show in their raw form here.") - - return 0 + return packerStarter.InspectConfig(packer.InspectConfigOptions{ + Ui: c.Ui, + }) } func (*InspectCommand) Help() string { diff --git a/command/inspect_test.go b/command/inspect_test.go new file mode 100644 index 000000000..d47dcf0d9 --- /dev/null +++ b/command/inspect_test.go @@ -0,0 +1 @@ +package command diff --git a/hcl2template/types.packer_config.go b/hcl2template/types.packer_config.go index e817685fc..6f27fedf4 100644 --- a/hcl2template/types.packer_config.go +++ b/hcl2template/types.packer_config.go @@ -439,24 +439,62 @@ func (p *PackerConfig) EvaluateExpression(line string) (out string, exit bool, d case line == "help": return PackerConsoleHelp, false, nil case line == "variables": - out := &strings.Builder{} - out.WriteString("> input-variables:\n\n") - for _, v := range p.InputVariables { - val, _ := v.Value() - fmt.Fprintf(out, "var.%s: %q [debug: %#v]\n", v.Name, PrintableCtyValue(val), v) - } - out.WriteString("\n> local-variables:\n\n") - for _, v := range p.LocalVariables { - val, _ := v.Value() - fmt.Fprintf(out, "local.%s: %q\n", v.Name, PrintableCtyValue(val)) - } - - return out.String(), false, nil + return p.printVariables(), false, nil default: return p.handleEval(line) } } +func (p *PackerConfig) printVariables() string { + out := &strings.Builder{} + out.WriteString("> input-variables:\n\n") + for _, v := range p.InputVariables { + val, _ := v.Value() + fmt.Fprintf(out, "var.%s: %q [debug: %#v]\n", v.Name, PrintableCtyValue(val), v) + } + out.WriteString("\n> local-variables:\n\n") + for _, v := range p.LocalVariables { + val, _ := v.Value() + fmt.Fprintf(out, "local.%s: %q\n", v.Name, PrintableCtyValue(val)) + } + return out.String() +} + +func (p *PackerConfig) printBuilds() string { + out := &strings.Builder{} + out.WriteString("> builds:\n") + for i, build := range p.Builds { + name := build.Name + if name == "" { + name = fmt.Sprintf("", i) + } + fmt.Fprintf(out, "\n > %s:\n", name) + fmt.Fprintf(out, "\n provisioners:\n\n") + if len(build.ProvisionerBlocks) == 0 { + fmt.Fprintf(out, " \n") + } + for _, prov := range build.ProvisionerBlocks { + str := prov.PType + if prov.PName != "" { + str = strings.Join([]string{prov.PType, prov.PName}, ".") + } + fmt.Fprintf(out, " %s\n", str) + } + fmt.Fprintf(out, "\n post-processors:\n\n") + if len(build.PostProcessors) == 0 { + fmt.Fprintf(out, " \n") + } + for _, pp := range build.PostProcessors { + str := strings.Join([]string{pp.PType, pp.PName}, ".") + if pp.PName != "" { + str = strings.Join([]string{pp.PType, pp.PName}, ".") + } + fmt.Fprintf(out, " %s\n", str) + } + } + return out.String() +} + func (p *PackerConfig) handleEval(line string) (out string, exit bool, diags hcl.Diagnostics) { // Parse the given line as an expression @@ -479,3 +517,12 @@ func (p *PackerConfig) FixConfig(_ packer.FixConfigOptions) (diags hcl.Diagnosti // No Fixers exist for HCL2 configs so there is nothing to do here for now. return } + +func (p *PackerConfig) InspectConfig(opts packer.InspectConfigOptions) int { + + ui := opts.Ui + ui.Say("Packer Inspect: HCL2 mode\n") + ui.Say(p.printVariables()) + ui.Say(p.printBuilds()) + return 0 +} diff --git a/packer/core.go b/packer/core.go index 1198cc341..262800a69 100644 --- a/packer/core.go +++ b/packer/core.go @@ -424,6 +424,129 @@ func (c *Core) EvaluateExpression(line string) (string, bool, hcl.Diagnostics) { } } +func (c *Core) InspectConfig(opts InspectConfigOptions) int { + + // Convenience... + ui := opts.Ui + tpl := c.Template + ui.Say("Packer Inspect: JSON mode") + + // Description + if tpl.Description != "" { + ui.Say("Description:\n") + ui.Say(tpl.Description + "\n") + } + + // Variables + if len(tpl.Variables) == 0 { + ui.Say("Variables:\n") + ui.Say(" ") + } else { + requiredHeader := false + for k, v := range tpl.Variables { + for _, sensitive := range tpl.SensitiveVariables { + if ok := strings.Compare(sensitive.Default, v.Default); ok == 0 { + v.Default = "" + } + } + if v.Required { + if !requiredHeader { + requiredHeader = true + ui.Say("Required variables:\n") + } + + ui.Machine("template-variable", k, v.Default, "1") + ui.Say(" " + k) + } + } + + if requiredHeader { + ui.Say("") + } + + ui.Say("Optional variables and their defaults:\n") + keys := make([]string, 0, len(tpl.Variables)) + max := 0 + for k := range tpl.Variables { + keys = append(keys, k) + if len(k) > max { + max = len(k) + } + } + + sort.Strings(keys) + + for _, k := range keys { + v := tpl.Variables[k] + if v.Required { + continue + } + for _, sensitive := range tpl.SensitiveVariables { + if ok := strings.Compare(sensitive.Default, v.Default); ok == 0 { + v.Default = "" + } + } + + padding := strings.Repeat(" ", max-len(k)) + output := fmt.Sprintf(" %s%s = %s", k, padding, v.Default) + + ui.Machine("template-variable", k, v.Default, "0") + ui.Say(output) + } + } + + ui.Say("") + + // Builders + ui.Say("Builders:\n") + if len(tpl.Builders) == 0 { + ui.Say(" ") + } else { + keys := make([]string, 0, len(tpl.Builders)) + max := 0 + for k := range tpl.Builders { + keys = append(keys, k) + if len(k) > max { + max = len(k) + } + } + + sort.Strings(keys) + + for _, k := range keys { + v := tpl.Builders[k] + padding := strings.Repeat(" ", max-len(k)) + output := fmt.Sprintf(" %s%s", k, padding) + if v.Name != v.Type { + output = fmt.Sprintf("%s (%s)", output, v.Type) + } + + ui.Machine("template-builder", k, v.Type) + ui.Say(output) + + } + } + + ui.Say("") + + // Provisioners + ui.Say("Provisioners:\n") + if len(tpl.Provisioners) == 0 { + ui.Say(" ") + } else { + for _, v := range tpl.Provisioners { + ui.Machine("template-provisioner", v.Type) + ui.Say(fmt.Sprintf(" %s", v.Type)) + } + } + + ui.Say("\nNote: If your build names contain user variables or template\n" + + "functions such as 'timestamp', these are processed at build time,\n" + + "and therefore only show in their raw form here.") + + return 0 +} + func (c *Core) FixConfig(opts FixConfigOptions) hcl.Diagnostics { var diags hcl.Diagnostics diff --git a/packer/run_interfaces.go b/packer/run_interfaces.go index ae146d6bc..31a0dedcd 100644 --- a/packer/run_interfaces.go +++ b/packer/run_interfaces.go @@ -29,6 +29,7 @@ type Handler interface { Evaluator BuildGetter ConfigFixer + ConfigInspector } //go:generate enumer -type FixConfigMode @@ -52,3 +53,12 @@ type ConfigFixer interface { // FixConfig will output the config in a fixed manner. FixConfig(FixConfigOptions) hcl.Diagnostics } + +type InspectConfigOptions struct { + Ui +} + +type ConfigInspector interface { + // Inspect will output self inspection for a configuration + InspectConfig(InspectConfigOptions) (ret int) +}