Merge pull request #9468 from hashicorp/azr-ft-hcl2-inspect
[hcl2] inspect command
This commit is contained in:
commit
7eda9eacc6
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(" <No variables>")
|
||||
} 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 = "<sensitive>"
|
||||
}
|
||||
}
|
||||
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 = "<sensitive>"
|
||||
}
|
||||
}
|
||||
|
||||
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(" <No builders>")
|
||||
} 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(" <No provisioners>")
|
||||
} 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 {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
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:
|
||||
|
||||
> <unnamed build 0>:
|
||||
|
||||
sources:
|
||||
|
||||
null.builder
|
||||
|
||||
provisioners:
|
||||
|
||||
shell-local
|
||||
|
||||
post-processors:
|
||||
|
||||
<no post-processor>
|
||||
|
||||
`},
|
||||
{[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "inspect", "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:
|
||||
|
||||
`},
|
||||
{[]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
|
||||
|
||||
`},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
|
||||
build {
|
||||
name = "aws_example_builder"
|
||||
description = <<EOF
|
||||
The builder of clouds !!
|
||||
|
||||
Use it at will.
|
||||
EOF
|
||||
|
||||
sources = [
|
||||
"source.amazon-ebs.example-1",
|
||||
|
||||
// this one is not defined but we don't want to error there, we just
|
||||
// would like to show what sources are being referenced.
|
||||
"source.amazon-ebs.example-2",
|
||||
]
|
||||
|
||||
provisioner "shell" {
|
||||
files = [
|
||||
"bins/install-this.sh",
|
||||
"bins/install-that.sh",
|
||||
"bins/conf-this.sh",
|
||||
]
|
||||
}
|
||||
|
||||
post-processor "manifest" {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
variable "fruit" {
|
||||
type = string
|
||||
default = "banana"
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -439,24 +439,72 @@ 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("<unnamed build %d>", 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 <no source>\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, " <no provisioner>\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, " <no post-processor>\n")
|
||||
}
|
||||
for _, pp := range build.PostProcessors {
|
||||
str := pp.PType
|
||||
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 +527,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
|
||||
}
|
||||
|
|
123
packer/core.go
123
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(" <No variables>")
|
||||
} 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 = "<sensitive>"
|
||||
}
|
||||
}
|
||||
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 = "<sensitive>"
|
||||
}
|
||||
}
|
||||
|
||||
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(" <No builders>")
|
||||
} 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(" <No provisioners>")
|
||||
} 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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue