From a96a8f22b97ea39732f2a79dafe9ca40e18e3cb6 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 23 Jun 2020 11:58:34 +0200 Subject: [PATCH 1/5] HCL2: allow to describe a build block --- hcl2template/types.build.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hcl2template/types.build.go b/hcl2template/types.build.go index 829038c85..041c5cfb6 100644 --- a/hcl2template/types.build.go +++ b/hcl2template/types.build.go @@ -38,6 +38,10 @@ type BuildBlock struct { // Name is a string representing the named build to show in the logs Name string + // A description of what this build does, it could be used in a inspect + // call for example. + Description string + // Sources is the list of sources that we want to start in this build block. Sources []SourceRef @@ -61,6 +65,7 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti var b struct { Name string `hcl:"name,optional"` + Description string `hcl:"description,optional"` FromSources []string `hcl:"sources,optional"` Config hcl.Body `hcl:",remain"` } @@ -70,6 +75,7 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti } build.Name = b.Name + build.Description = b.Description for _, buildFrom := range b.FromSources { ref := sourceRefFromString(buildFrom) From 51d02f8c2dd617f459082dbb12b8ec4f28e2eea2 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 23 Jun 2020 11:58:57 +0200 Subject: [PATCH 2/5] hcl2: inspect command --- command/cli.go | 9 ++ command/console_test.go | 2 +- command/inspect.go | 160 +++++----------------------- command/inspect_test.go | 1 + hcl2template/types.packer_config.go | 73 ++++++++++--- packer/core.go | 123 +++++++++++++++++++++ packer/run_interfaces.go | 10 ++ 7 files changed, 232 insertions(+), 146 deletions(-) create mode 100644 command/inspect_test.go 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) + ctx := context.Background() + + cfg, ret := c.ParseArgs(args) + if ret != 0 { + return ret + } + + return c.RunContext(ctx, cfg) +} + +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 1 + return &cfg, 1 } args = flags.Args() - if len(args) != 1 { - flags.Usage() - return 1 + if len(args) == 1 { + cfg.Path = args[0] } + return &cfg, 0 +} - // 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 +func (c *InspectCommand) RunContext(ctx context.Context, cla *InspectArgs) int { + packerStarter, ret := c.GetConfig(&cla.MetaArgs) + if ret != 0 { + return ret } - - // Convenience... - ui := c.Ui - - // 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 + 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) +} From f98576b19ebef75d2ffb8a09cee715f287e18ce0 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 23 Jun 2020 15:13:53 +0200 Subject: [PATCH 3/5] add a basic test for the inspect command --- command/exec_test.go | 2 ++ command/inspect_test.go | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/command/exec_test.go b/command/exec_test.go index 2ffc7fefa..d9508a090 100644 --- a/command/exec_test.go +++ b/command/exec_test.go @@ -85,6 +85,8 @@ func TestHelperProcess(*testing.T) { switch cmd { case "console": os.Exit((&ConsoleCommand{Meta: commandMeta()}).Run(args)) + case "inspect": + os.Exit((&InspectCommand{Meta: commandMeta()}).Run(args)) default: fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) os.Exit(2) diff --git a/command/inspect_test.go b/command/inspect_test.go index d47dcf0d9..35575e2c9 100644 --- a/command/inspect_test.go +++ b/command/inspect_test.go @@ -1 +1,57 @@ package command + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_commands(t *testing.T) { + + tc := []struct { + command []string + env []string + expected string + }{ + {[]string{"inspect", "-var=fruit=banana", filepath.Join(testFixture("var-arg"), "fruit_builder.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode + +> input-variables: + +var.fruit: "banana" [debug: {Type:cty.String,CmdValue:banana,VarfileValue:null,EnvValue:null,DefaultValue:null}] + +> local-variables: + +local.fruit: "banana" + +> builds: + + > : + + provisioners: + + shell-local + + post-processors: + + + +`}, + } + + for _, tc := range tc { + t.Run(fmt.Sprintf("packer %s", tc.command), func(t *testing.T) { + p := helperCommand(t, tc.command...) + p.Env = append(p.Env, tc.env...) + bs, err := p.Output() + if err != nil { + t.Fatalf("%v: %s", err, bs) + } + actual := string(bs) + if diff := cmp.Diff(tc.expected, actual); diff != "" { + t.Fatalf("unexpected ouput %s", diff) + } + }) + } +} From dabcc866e5ebf0e0ed120515c5e5ef727923d13c Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 23 Jun 2020 15:17:49 +0200 Subject: [PATCH 4/5] add another inspect test --- command/inspect_test.go | 12 ++++++++++++ command/test-fixtures/hcl/vars/fruit_string.pkr.hcl | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 command/test-fixtures/hcl/vars/fruit_string.pkr.hcl diff --git a/command/inspect_test.go b/command/inspect_test.go index 35575e2c9..27f8474da 100644 --- a/command/inspect_test.go +++ b/command/inspect_test.go @@ -37,6 +37,18 @@ local.fruit: "banana" +`}, + {[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "vars", "fruit_string.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode + +> input-variables: + +var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,EnvValue:null,DefaultValue:banana}] + +> local-variables: + + +> builds: + `}, } diff --git a/command/test-fixtures/hcl/vars/fruit_string.pkr.hcl b/command/test-fixtures/hcl/vars/fruit_string.pkr.hcl new file mode 100644 index 000000000..3140d12e0 --- /dev/null +++ b/command/test-fixtures/hcl/vars/fruit_string.pkr.hcl @@ -0,0 +1,5 @@ + +variable "fruit" { + type = string + default = "banana" +} From 6f4d79799b8c3f0c487f4a14b74ef2468bd7e446 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 23 Jun 2020 15:40:54 +0200 Subject: [PATCH 5/5] add thorough test --- command/inspect_test.go | 39 ++++++++++++++++++- .../hcl/inspect/aws-builds.pkr.hcl | 29 ++++++++++++++ .../{vars => inspect}/fruit_string.pkr.hcl | 0 .../hcl/inspect/virtualbox-builds.pkr.hcl | 0 hcl2template/types.packer_config.go | 12 +++++- 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 command/test-fixtures/hcl/inspect/aws-builds.pkr.hcl rename command/test-fixtures/hcl/{vars => inspect}/fruit_string.pkr.hcl (100%) create mode 100644 command/test-fixtures/hcl/inspect/virtualbox-builds.pkr.hcl diff --git a/command/inspect_test.go b/command/inspect_test.go index 27f8474da..573b99680 100644 --- a/command/inspect_test.go +++ b/command/inspect_test.go @@ -29,6 +29,10 @@ local.fruit: "banana" > : + sources: + + null.builder + provisioners: shell-local @@ -38,7 +42,7 @@ local.fruit: "banana" `}, - {[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "vars", "fruit_string.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode + {[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "inspect", "fruit_string.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode > input-variables: @@ -49,6 +53,39 @@ var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,Env > builds: +`}, + {[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "inspect")}, nil, `Packer Inspect: HCL2 mode + +> input-variables: + +var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,EnvValue:null,DefaultValue:banana}] + +> local-variables: + + +> builds: + + > aws_example_builder: + + > Description: The builder of clouds !! + +Use it at will. + + + sources: + + amazon-ebs.example-1 + + amazon-ebs.example-2 + + provisioners: + + shell + + post-processors: + + manifest + `}, } diff --git a/command/test-fixtures/hcl/inspect/aws-builds.pkr.hcl b/command/test-fixtures/hcl/inspect/aws-builds.pkr.hcl new file mode 100644 index 000000000..60385b31f --- /dev/null +++ b/command/test-fixtures/hcl/inspect/aws-builds.pkr.hcl @@ -0,0 +1,29 @@ + + +build { + name = "aws_example_builder" + description = <", i) } fmt.Fprintf(out, "\n > %s:\n", name) + if build.Description != "" { + fmt.Fprintf(out, "\n > Description: %s\n", build.Description) + } + fmt.Fprintf(out, "\n sources:\n") + if len(build.Sources) == 0 { + fmt.Fprintf(out, "\n \n") + } + for _, source := range build.Sources { + fmt.Fprintf(out, "\n %s\n", source) + } fmt.Fprintf(out, "\n provisioners:\n\n") if len(build.ProvisionerBlocks) == 0 { fmt.Fprintf(out, " \n") @@ -485,7 +495,7 @@ func (p *PackerConfig) printBuilds() string { fmt.Fprintf(out, " \n") } for _, pp := range build.PostProcessors { - str := strings.Join([]string{pp.PType, pp.PName}, ".") + str := pp.PType if pp.PName != "" { str = strings.Join([]string{pp.PType, pp.PName}, ".") }