Merge pull request #9139 from hashicorp/hcl2_commands_refactor

Hcl2 commands refactor
This commit is contained in:
Megan Marsh 2020-05-14 11:16:12 -07:00 committed by GitHub
commit 533fbc1381
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 562 additions and 520 deletions

View File

@ -39,6 +39,8 @@
the service names things [GH-9078]
* core/hcl2: Maps are now treated as settable arguments as opposed to blocks.
For example `tags = {}` instead of `tags {}` [GH-9035]
* `packer build` command: removed option to set `parallel=false`, use
`-parallel-builds=1` for this.
### FEATURES:
* **New Builder** azure-dtl allows creation of devtestlabs images in Azure

View File

@ -6,17 +6,13 @@ import (
"fmt"
"log"
"math"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/helper/enumflag"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template"
"golang.org/x/sync/semaphore"
@ -29,60 +25,26 @@ type BuildCommand struct {
}
func (c *BuildCommand) Run(args []string) int {
buildCtx, cancelBuildCtx := context.WithCancel(context.Background())
// Handle interrupts for this build
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
defer func() {
cancelBuildCtx()
signal.Stop(sigCh)
close(sigCh)
}()
go func() {
select {
case sig := <-sigCh:
if sig == nil {
// context got cancelled and this closed chan probably
// triggered first
return
}
c.Ui.Error(fmt.Sprintf("Cancelling build after receiving %s", sig))
cancelBuildCtx()
case <-buildCtx.Done():
}
}()
ctx, cleanup := handleTermInterrupt(c.Ui)
defer cleanup()
return c.RunContext(buildCtx, args)
cfg, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
return c.RunContext(ctx, cfg)
}
// Config is the command-configuration parsed from the command line.
type Config struct {
Color, Debug, Force, Timestamp bool
ParallelBuilds int64
OnError string
Path string
}
func (c *BuildCommand) ParseArgs(args []string) (Config, int) {
var cfg Config
var parallel bool
func (c *BuildCommand) ParseArgs(args []string) (*BuildArgs, int) {
var cfg BuildArgs
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) }
flags.BoolVar(&cfg.Color, "color", true, "")
flags.BoolVar(&cfg.Debug, "debug", false, "")
flags.BoolVar(&cfg.Force, "force", false, "")
flags.BoolVar(&cfg.Timestamp, "timestamp-ui", false, "")
flagOnError := enumflag.New(&cfg.OnError, "cleanup", "abort", "ask")
flags.Var(flagOnError, "on-error", "")
flags.BoolVar(&parallel, "parallel", true, "")
flags.Int64Var(&cfg.ParallelBuilds, "parallel-builds", 0, "")
cfg.AddFlagSets(flags)
if err := flags.Parse(args); err != nil {
return cfg, 1
return &cfg, 1
}
if parallel == false && cfg.ParallelBuilds == 0 {
cfg.ParallelBuilds = 1
}
if cfg.ParallelBuilds < 1 {
cfg.ParallelBuilds = math.MaxInt64
}
@ -90,101 +52,98 @@ func (c *BuildCommand) ParseArgs(args []string) (Config, int) {
args = flags.Args()
if len(args) != 1 {
flags.Usage()
return cfg, 1
return &cfg, 1
}
cfg.Path = args[0]
return cfg, 0
return &cfg, 0
}
func (c *BuildCommand) GetBuildsFromHCL(path string) ([]packer.Build, int) {
func (m *Meta) GetConfigFromHCL(cla *MetaArgs) (packer.BuildGetter, int) {
parser := &hcl2template.Parser{
Parser: hclparse.NewParser(),
BuilderSchemas: c.CoreConfig.Components.BuilderStore,
ProvisionersSchemas: c.CoreConfig.Components.ProvisionerStore,
PostProcessorsSchemas: c.CoreConfig.Components.PostProcessorStore,
BuilderSchemas: m.CoreConfig.Components.BuilderStore,
ProvisionersSchemas: m.CoreConfig.Components.ProvisionerStore,
PostProcessorsSchemas: m.CoreConfig.Components.PostProcessorStore,
}
builds, diags := parser.Parse(path, c.varFiles, c.flagVars, c.CoreConfig.Only, c.CoreConfig.Except)
{
// write HCL errors/diagnostics if any.
b := bytes.NewBuffer(nil)
err := hcl.NewDiagnosticTextWriter(b, parser.Files(), 80, false).WriteDiagnostics(diags)
if err != nil {
c.Ui.Error("could not write diagnostic: " + err.Error())
return nil, 1
}
if b.Len() != 0 {
c.Ui.Message(b.String())
}
}
ret := 0
if diags.HasErrors() {
ret = 1
}
return builds, ret
cfg, diags := parser.Parse(cla.Path, cla.VarFiles, cla.Vars)
return cfg, writeDiags(m.Ui, parser.Files(), diags)
}
func (c *BuildCommand) GetBuilds(path string) ([]packer.Build, int) {
func writeDiags(ui packer.Ui, files map[string]*hcl.File, diags hcl.Diagnostics) int {
// write HCL errors/diagnostics if any.
b := bytes.NewBuffer(nil)
err := hcl.NewDiagnosticTextWriter(b, files, 80, false).WriteDiagnostics(diags)
if err != nil {
ui.Error("could not write diagnostic: " + err.Error())
return 1
}
if b.Len() != 0 {
if diags.HasErrors() {
ui.Error(b.String())
return 1
}
ui.Say(b.String())
}
return 0
}
isHCLLoaded, err := isHCLLoaded(path)
if path != "-" && err != nil {
c.Ui.Error(fmt.Sprintf("could not tell whether %s is hcl enabled: %s", path, err))
func (m *Meta) GetConfig(cla *MetaArgs) (packer.BuildGetter, int) {
cfgType, err := ConfigType(cla.Path)
if err != nil {
m.Ui.Error(fmt.Sprintf("could not tell config type: %s", err))
return nil, 1
}
if isHCLLoaded {
return c.GetBuildsFromHCL(path)
switch cfgType {
case "hcl":
// TODO(azr): allow to pass a slice of files here.
return m.GetConfigFromHCL(cla)
default:
// TODO: uncomment once we've polished HCL a bit more.
// c.Ui.Say(`Legacy JSON Configuration Will Be Used.
// The template will be parsed in the legacy configuration style. This style
// will continue to work but users are encouraged to move to the new style.
// See: https://packer.io/guides/hcl
// `)
return m.GetConfigFromJSON(cla)
}
}
// TODO: uncomment in v1.5.1 once we've polished HCL a bit more.
// c.Ui.Say(`Legacy JSON Configuration Will Be Used.
// The template will be parsed in the legacy configuration style. This style
// will continue to work but users are encouraged to move to the new style.
// See: https://packer.io/guides/hcl
// `)
func (m *Meta) GetConfigFromJSON(cla *MetaArgs) (packer.BuildGetter, int) {
// Parse the template
var tpl *template.Template
tpl, err = template.ParseFile(path)
tpl, err := template.ParseFile(cla.Path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
m.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
return nil, 1
}
// Get the core
core, err := c.Meta.Core(tpl)
if err != nil {
c.Ui.Error(err.Error())
return nil, 1
}
core, err := m.Core(tpl, cla)
ret := 0
buildNames := c.Meta.BuildNames(core)
builds := make([]packer.Build, 0, len(buildNames))
for _, n := range buildNames {
b, err := core.Build(n)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Failed to initialize build '%s': %s",
n, err))
ret = 1
continue
}
builds = append(builds, b)
if err != nil {
m.Ui.Error(err.Error())
ret = 1
}
return builds, ret
return core, ret
}
func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
cfg, ret := c.ParseArgs(args)
func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int {
packerStarter, ret := c.GetConfig(&cla.MetaArgs)
if ret != 0 {
return ret
}
builds, ret := c.GetBuilds(cfg.Path)
builds, diags := packerStarter.GetBuilds(packer.GetBuildsOptions{
Only: cla.Only,
Except: cla.Except,
})
if cfg.Debug {
// here, something could have gone wrong but we still want to run valid
// builds.
ret = writeDiags(c.Ui, nil, diags)
if cla.Debug {
c.Ui.Say("Debug mode enabled. Builds will not be parallelized.")
}
@ -199,7 +158,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
buildUis := make(map[packer.Build]packer.Ui)
for i := range builds {
ui := c.Ui
if cfg.Color {
if cla.Color {
ui = &packer.ColoredUi{
Color: colors[i%len(colors)],
Ui: ui,
@ -213,7 +172,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
}
}
// Now add timestamps if requested
if cfg.Timestamp {
if cla.TimestampUi {
ui = &packer.TimestampedUi{
Ui: ui,
}
@ -222,17 +181,17 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
buildUis[builds[i]] = ui
}
log.Printf("Build debug mode: %v", cfg.Debug)
log.Printf("Force build: %v", cfg.Force)
log.Printf("On error: %v", cfg.OnError)
log.Printf("Build debug mode: %v", cla.Debug)
log.Printf("Force build: %v", cla.Force)
log.Printf("On error: %v", cla.OnError)
// Set the debug and force mode and prepare all the builds
for i := range builds {
b := builds[i]
log.Printf("Preparing build: %s", b.Name())
b.SetDebug(cfg.Debug)
b.SetForce(cfg.Force)
b.SetOnError(cfg.OnError)
b.SetDebug(cla.Debug)
b.SetForce(cla.Force)
b.SetOnError(cla.OnError)
warnings, err := b.Prepare()
if err != nil {
@ -260,7 +219,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
sync.RWMutex
m map[string]error
}{m: make(map[string]error)}
limitParallel := semaphore.NewWeighted(cfg.ParallelBuilds)
limitParallel := semaphore.NewWeighted(cla.ParallelBuilds)
for i := range builds {
if err := buildCtx.Err(); err != nil {
log.Println("Interrupted, not going to start any more builds.")
@ -304,12 +263,12 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
}
}()
if cfg.Debug {
if cla.Debug {
log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name())
wg.Wait()
}
if cfg.ParallelBuilds == 1 {
if cla.ParallelBuilds == 1 {
log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name())
wg.Wait()
}
@ -415,8 +374,7 @@ Options:
-force Force a build to continue if artifacts exist, deletes existing artifacts.
-machine-readable Produce machine-readable output.
-on-error=[cleanup|abort|ask] If the build fails do: clean up (default), abort, or ask.
-parallel=false Disable parallelization. (Default: true)
-parallel-builds=1 Number of builds to run in parallel. 0 means no limit (Default: 0)
-parallel-builds=1 Number of builds to run in parallel. 1 disables parallelization. 0 means no limit (Default: 0)
-timestamp-ui Enable prefixing of each ui output with an RFC3339 timestamp.
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.

View File

@ -18,32 +18,32 @@ func TestBuildCommand_RunContext_CtxCancel(t *testing.T) {
expected int
}{
{"cancel 1 pending build - parallel=true",
[]string{"-parallel=true", filepath.Join(testFixture("parallel"), "1lock-5wg.json")},
[]string{"-parallel-builds=10", filepath.Join(testFixture("parallel"), "1lock-5wg.json")},
5,
1,
},
{"cancel in the middle with 2 pending builds - parallel=true",
[]string{"-parallel=true", filepath.Join(testFixture("parallel"), "2lock-4wg.json")},
[]string{"-parallel-builds=10", filepath.Join(testFixture("parallel"), "2lock-4wg.json")},
4,
1,
},
{"cancel 1 locked build - debug - parallel=true",
[]string{"-parallel=true", "-debug=true", filepath.Join(testFixture("parallel"), "1lock.json")},
[]string{"-parallel-builds=10", "-debug=true", filepath.Join(testFixture("parallel"), "1lock.json")},
0,
1,
},
{"cancel 2 locked builds - debug - parallel=true",
[]string{"-parallel=true", "-debug=true", filepath.Join(testFixture("parallel"), "2lock.json")},
[]string{"-parallel-builds=10", "-debug=true", filepath.Join(testFixture("parallel"), "2lock.json")},
0,
1,
},
{"cancel 1 locked build - debug - parallel=false",
[]string{"-parallel=false", "-debug=true", filepath.Join(testFixture("parallel"), "1lock.json")},
[]string{"-parallel-builds=1", "-debug=true", filepath.Join(testFixture("parallel"), "1lock.json")},
0,
1,
},
{"cancel 2 locked builds - debug - parallel=false",
[]string{"-parallel=false", "-debug=true", filepath.Join(testFixture("parallel"), "2lock.json")},
[]string{"-parallel-builds=1", "-debug=true", filepath.Join(testFixture("parallel"), "2lock.json")},
0,
1,
},
@ -62,7 +62,11 @@ func TestBuildCommand_RunContext_CtxCancel(t *testing.T) {
codeC := make(chan int)
go func() {
defer close(codeC)
codeC <- c.RunContext(ctx, tt.args)
cfg, ret := c.ParseArgs(tt.args)
if ret != 0 {
t.Fatal("ParseArgs failed.")
}
codeC <- c.RunContext(ctx, cfg)
}()
t.Logf("waiting for passing tests if any")
b.wg.Wait() // ran `tt.parallelPassingTests` times

View File

@ -11,7 +11,7 @@ func TestBuildWithCleanupScript(t *testing.T) {
}
args := []string{
"-parallel=false",
"-parallel-builds=1",
filepath.Join(testFixture("cleanup-script"), "template.json"),
}

View File

@ -95,7 +95,7 @@ func TestBuildParallel_1(t *testing.T) {
}
args := []string{
fmt.Sprintf("-parallel=true"),
fmt.Sprintf("-parallel-builds=10"),
filepath.Join(testFixture("parallel"), "1lock-5wg.json"),
}

View File

@ -224,7 +224,7 @@ func TestBuildOnlyFileCommaFlags(t *testing.T) {
}
args := []string{
"-parallel=false",
"-parallel-builds=1",
"-only=chocolate,vanilla",
filepath.Join(testFixture("build-only"), "template.json"),
}
@ -266,7 +266,7 @@ func TestBuildStdin(t *testing.T) {
defer func() { os.Stdin = stdin }()
defer cleanup()
if code := c.Run([]string{"-parallel=false", "-"}); code != 0 {
if code := c.Run([]string{"-parallel-builds=1", "-"}); code != 0 {
fatalCommand(t, c.Meta)
}
@ -284,7 +284,7 @@ func TestBuildOnlyFileMultipleFlags(t *testing.T) {
}
args := []string{
"-parallel=false",
"-parallel-builds=1",
"-only=chocolate",
"-only=cherry",
"-only=apple", // ignored
@ -345,7 +345,7 @@ func TestBuildEverything(t *testing.T) {
}
args := []string{
"-parallel=false",
"-parallel-builds=1",
`-except=`,
filepath.Join(testFixture("build-only"), "template.json"),
}
@ -370,7 +370,7 @@ func TestBuildExceptFileCommaFlags(t *testing.T) {
}
args := []string{
"-parallel=false",
"-parallel-builds=1",
"-except=chocolate,vanilla",
filepath.Join(testFixture("build-only"), "template.json"),
}
@ -401,7 +401,7 @@ func testHCLOnlyExceptFlags(t *testing.T, args, present, notPresent []string) {
defer cleanup()
finalArgs := []string{"-parallel=false"}
finalArgs := []string{"-parallel-builds=1"}
finalArgs = append(finalArgs, args...)
finalArgs = append(finalArgs, testFixture("hcl-only-except"))
@ -482,7 +482,7 @@ func TestBuildWithNonExistingBuilder(t *testing.T) {
}
args := []string{
"-parallel=false",
"-parallel-builds=1",
`-except=`,
filepath.Join(testFixture("build-only"), "not-found.json"),
}
@ -630,31 +630,31 @@ func TestBuildCommand_ParseArgs(t *testing.T) {
tests := []struct {
fields fields
args args
wantCfg Config
wantCfg *BuildArgs
wantExitCode int
}{
{fields{defaultMeta},
args{[]string{"file.json"}},
Config{
Path: "file.json",
&BuildArgs{
MetaArgs: MetaArgs{Path: "file.json"},
ParallelBuilds: math.MaxInt64,
Color: true,
},
0,
},
{fields{defaultMeta},
args{[]string{"-parallel=true", "file.json"}},
Config{
Path: "file.json",
ParallelBuilds: math.MaxInt64,
args{[]string{"-parallel-builds=10", "file.json"}},
&BuildArgs{
MetaArgs: MetaArgs{Path: "file.json"},
ParallelBuilds: 10,
Color: true,
},
0,
},
{fields{defaultMeta},
args{[]string{"-parallel=false", "file.json"}},
Config{
Path: "file.json",
args{[]string{"-parallel-builds=1", "file.json"}},
&BuildArgs{
MetaArgs: MetaArgs{Path: "file.json"},
ParallelBuilds: 1,
Color: true,
},
@ -662,17 +662,17 @@ func TestBuildCommand_ParseArgs(t *testing.T) {
},
{fields{defaultMeta},
args{[]string{"-parallel-builds=5", "file.json"}},
Config{
Path: "file.json",
&BuildArgs{
MetaArgs: MetaArgs{Path: "file.json"},
ParallelBuilds: 5,
Color: true,
},
0,
},
{fields{defaultMeta},
args{[]string{"-parallel=false", "-parallel-builds=5", "otherfile.json"}},
Config{
Path: "otherfile.json",
args{[]string{"-parallel-builds=1", "-parallel-builds=5", "otherfile.json"}},
&BuildArgs{
MetaArgs: MetaArgs{Path: "otherfile.json"},
ParallelBuilds: 5,
Color: true,
},

105
command/cli.go Normal file
View File

@ -0,0 +1,105 @@
package command
import (
"flag"
"fmt"
"strings"
"github.com/hashicorp/packer/helper/enumflag"
kvflag "github.com/hashicorp/packer/helper/flag-kv"
sliceflag "github.com/hashicorp/packer/helper/flag-slice"
)
// ConfigType tells what type of config we should use, it can return values
// like "hcl" or "json".
// Make sure Args was correctly set before.
func ConfigType(args ...string) (string, error) {
switch len(args) {
// TODO(azr): in the future, I want to allow passing multiple arguments to
// merge HCL confs together; but this will probably need an RFC first.
case 1:
name := args[0]
if name == "-" {
// TODO(azr): To allow piping HCL2 confs (when args is "-"), we probably
// will need to add a setting that says "this is an HCL config".
return "json", nil
}
if strings.HasSuffix(name, ".pkr.hcl") ||
strings.HasSuffix(name, ".pkr.json") {
return "hcl", nil
}
isDir, err := isDir(name)
if isDir {
return "hcl", err
}
return "json", err
default:
return "", fmt.Errorf("packer only takes one argument: %q", args)
}
}
// NewMetaArgs parses cli args and put possible values
func (ma *MetaArgs) AddFlagSets(fs *flag.FlagSet) {
fs.Var((*sliceflag.StringFlag)(&ma.Only), "only", "")
fs.Var((*sliceflag.StringFlag)(&ma.Except), "except", "")
fs.Var((*kvflag.Flag)(&ma.Vars), "var", "")
fs.Var((*kvflag.StringSlice)(&ma.VarFiles), "var-file", "")
}
// MetaArgs defines commonalities between all comands
type MetaArgs struct {
Path string
Only, Except []string
Vars map[string]string
VarFiles []string
}
func (ba *BuildArgs) AddFlagSets(flags *flag.FlagSet) {
flags.BoolVar(&ba.Color, "color", true, "")
flags.BoolVar(&ba.Debug, "debug", false, "")
flags.BoolVar(&ba.Force, "force", false, "")
flags.BoolVar(&ba.TimestampUi, "timestamp-ui", false, "")
flags.BoolVar(&ba.MachineReadable, "machine-readable", false, "")
flags.Int64Var(&ba.ParallelBuilds, "parallel-builds", 0, "")
flagOnError := enumflag.New(&ba.OnError, "cleanup", "abort", "ask")
flags.Var(flagOnError, "on-error", "")
ba.MetaArgs.AddFlagSets(flags)
}
// BuildArgs represents a parsed cli line for a `packer build`
type BuildArgs struct {
MetaArgs
Color, Debug, Force, TimestampUi, MachineReadable bool
ParallelBuilds int64
OnError string
}
// ConsoleArgs represents a parsed cli line for a `packer console`
type ConsoleArgs struct{ MetaArgs }
func (fa *FixArgs) AddFlagSets(flags *flag.FlagSet) {
flags.BoolVar(&fa.Validate, "validate", true, "")
fa.MetaArgs.AddFlagSets(flags)
}
// FixArgs represents a parsed cli line for a `packer fix`
type FixArgs struct {
MetaArgs
Validate bool
}
func (va *ValidateArgs) AddFlagSets(flags *flag.FlagSet) {
flags.BoolVar(&va.SyntaxOnly, "syntax-only", false, "check syntax only")
va.MetaArgs.AddFlagSets(flags)
}
// ValidateArgs represents a parsed cli line for a `packer validate`
type ValidateArgs struct {
MetaArgs
SyntaxOnly bool
}

View File

@ -2,6 +2,7 @@ package command
import (
"bufio"
"context"
"errors"
"fmt"
"io"
@ -30,16 +31,33 @@ type ConsoleCommand struct {
}
func (c *ConsoleCommand) Run(args []string) int {
flags := c.Meta.FlagSet("console", FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
ctx := context.Background()
cfg, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
var templ *template.Template
return c.RunContext(ctx, cfg)
}
func (c *ConsoleCommand) ParseArgs(args []string) (*ConsoleArgs, int) {
var cfg ConsoleArgs
flags := c.Meta.FlagSet("console", FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) }
cfg.AddFlagSets(flags)
if err := flags.Parse(args); err != nil {
return &cfg, 1
}
args = flags.Args()
if len(args) < 1 {
return &cfg, 0
}
func (c *ConsoleCommand) RunContext(ctx context.Context, cla *ConsoleArgs) int {
var templ *template.Template
if cla.Path == "" {
// If user has not defined a builder, create a tiny null placeholder
// builder so that we can properly initialize the core
tpl, err := template.Parse(strings.NewReader(TiniestBuilder))
@ -48,22 +66,18 @@ func (c *ConsoleCommand) Run(args []string) int {
return 1
}
templ = tpl
} else if len(args) == 1 {
} else {
// Parse the provided template
tpl, err := template.ParseFile(args[0])
tpl, err := template.ParseFile(cla.Path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
return 1
}
templ = tpl
} else {
// User provided too many arguments
flags.Usage()
return 1
}
// Get the core
core, err := c.Meta.Core(templ)
core, err := c.Meta.Core(templ, &cla.MetaArgs)
if err != nil {
c.Ui.Error(err.Error())
return 1

View File

@ -2,6 +2,7 @@ package command
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
@ -19,22 +20,38 @@ type FixCommand struct {
}
func (c *FixCommand) Run(args []string) int {
var flagValidate bool
ctx, cleanup := handleTermInterrupt(c.Ui)
defer cleanup()
cfg, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
return c.RunContext(ctx, cfg)
}
func (c *FixCommand) ParseArgs(args []string) (*FixArgs, int) {
var cfg FixArgs
flags := c.Meta.FlagSet("fix", FlagSetNone)
flags.BoolVar(&flagValidate, "validate", true, "")
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
return &cfg, 1
}
cfg.Path = args[0]
return &cfg, 0
}
func (c *FixCommand) RunContext(ctx context.Context, cla *FixArgs) int {
// Read the file for decoding
tplF, err := os.Open(args[0])
tplF, err := os.Open(cla.Path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error opening template: %s", err))
return 1
@ -86,25 +103,27 @@ func (c *FixCommand) Run(args []string) int {
result = strings.Replace(result, `\u003e`, ">", -1)
c.Ui.Say(result)
if flagValidate {
// Attempt to parse and validate the template
tpl, err := template.Parse(strings.NewReader(result))
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error! Fixed template fails to parse: %s\n\n"+
"This is usually caused by an error in the input template.\n"+
"Please fix the error and try again.",
err))
return 1
}
if err := tpl.Validate(); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error! Fixed template failed to validate: %s\n\n"+
"This is usually caused by an error in the input template.\n"+
"Please fix the error and try again.",
err))
return 1
}
if cla.Validate == false {
return 0
}
// Attempt to parse and validate the template
tpl, err := template.Parse(strings.NewReader(result))
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error! Fixed template fails to parse: %s\n\n"+
"This is usually caused by an error in the input template.\n"+
"Please fix the error and try again.",
err))
return 1
}
if err := tpl.Validate(); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error! Fixed template failed to validate: %s\n\n"+
"This is usually caused by an error in the input template.\n"+
"Please fix the error and try again.",
err))
return 1
}
return 0

View File

@ -8,7 +8,6 @@ import (
"os"
kvflag "github.com/hashicorp/packer/helper/flag-kv"
sliceflag "github.com/hashicorp/packer/helper/flag-slice"
"github.com/hashicorp/packer/helper/wrappedstreams"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template"
@ -30,22 +29,18 @@ type Meta struct {
CoreConfig *packer.CoreConfig
Ui packer.Ui
Version string
// These are set by command-line flags
varFiles []string
flagVars map[string]string
}
// Core returns the core for the given template given the configured
// CoreConfig and user variables on this Meta.
func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
func (m *Meta) Core(tpl *template.Template, cla *MetaArgs) (*packer.Core, error) {
// Copy the config so we don't modify it
config := *m.CoreConfig
config.Template = tpl
fj := &kvflag.FlagJSON{}
// First populate fj with contents from var files
for _, file := range m.varFiles {
for _, file := range cla.VarFiles {
err := fj.Set(file)
if err != nil {
return nil, err
@ -54,15 +49,15 @@ func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
// Now read fj values back into flagvars and set as config.Variables. Only
// add to flagVars if the key doesn't already exist, because flagVars comes
// from the command line and should not be overridden by variable files.
if m.flagVars == nil {
m.flagVars = map[string]string{}
if cla.Vars == nil {
cla.Vars = map[string]string{}
}
for k, v := range *fj {
if _, exists := m.flagVars[k]; !exists {
m.flagVars[k] = v
if _, exists := cla.Vars[k]; !exists {
cla.Vars[k] = v
}
}
config.Variables = m.flagVars
config.Variables = cla.Vars
// Init the core
core, err := packer.NewCore(&config)
@ -73,54 +68,6 @@ func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
return core, nil
}
// BuildNames returns the list of builds that are in the given core
// that we care about taking into account the only and except flags.
func (m *Meta) BuildNames(c *packer.Core) []string {
// TODO: test
// Filter the "only"
if len(m.CoreConfig.Only) > 0 {
// Build a set of all the available names
nameSet := make(map[string]struct{})
for _, n := range c.BuildNames() {
nameSet[n] = struct{}{}
}
// Build our result set which we pre-allocate some sane number
result := make([]string, 0, len(m.CoreConfig.Only))
for _, n := range m.CoreConfig.Only {
if _, ok := nameSet[n]; ok {
result = append(result, n)
}
}
return result
}
// Filter the "except"
if len(m.CoreConfig.Except) > 0 {
// Build a set of the things we don't want
nameSet := make(map[string]struct{})
for _, n := range m.CoreConfig.Except {
nameSet[n] = struct{}{}
}
// Build our result set which is the names of all builds except
// those in the given set.
names := c.BuildNames()
result := make([]string, 0, len(names))
for _, n := range names {
if _, ok := nameSet[n]; !ok {
result = append(result, n)
}
}
return result
}
// We care about everything
return c.BuildNames()
}
// FlagSet returns a FlagSet with the common flags that every
// command implements. The exact behavior of FlagSet can be configured
// using the flags as the second parameter, for example to disable
@ -128,19 +75,6 @@ func (m *Meta) BuildNames(c *packer.Core) []string {
func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
f := flag.NewFlagSet(n, flag.ContinueOnError)
// FlagSetBuildFilter tells us to enable the settings for selecting
// builds we care about.
if fs&FlagSetBuildFilter != 0 {
f.Var((*sliceflag.StringFlag)(&m.CoreConfig.Except), "except", "")
f.Var((*sliceflag.StringFlag)(&m.CoreConfig.Only), "only", "")
}
// FlagSetVars tells us what variables to use
if fs&FlagSetVars != 0 {
f.Var((*kvflag.Flag)(&m.flagVars), "var", "")
f.Var((*kvflag.StringSlice)(&m.varFiles), "var-file", "")
}
// Create an io.Writer that writes to our Ui properly for errors.
// This is kind of a hack, but it does the job. Basically: create
// a pipe, use a scanner to break it into lines, and output each line

37
command/signal.go Normal file
View File

@ -0,0 +1,37 @@
package command
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/hashicorp/packer/packer"
)
func handleTermInterrupt(ui packer.Ui) (context.Context, func()) {
ctx, cancelCtx := context.WithCancel(context.Background())
// Handle interrupts for this build
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
cleanup := func() {
cancelCtx()
signal.Stop(sigCh)
close(sigCh)
}
go func() {
select {
case sig := <-sigCh:
if sig == nil {
// context got cancelled and this closed chan probably
// triggered first
return
}
ui.Error(fmt.Sprintf("Cancelling build after receiving %s", sig))
cancelCtx()
case <-ctx.Done():
}
}()
return ctx, cleanup
}

View File

@ -1,6 +1,7 @@
package command
import (
"context"
"encoding/json"
"fmt"
"log"
@ -19,35 +20,52 @@ type ValidateCommand struct {
}
func (c *ValidateCommand) Run(args []string) int {
var cfgSyntaxOnly bool
ctx, cleanup := handleTermInterrupt(c.Ui)
defer cleanup()
cfg, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
return c.RunContext(ctx, cfg)
}
func (c *ValidateCommand) ParseArgs(args []string) (*ValidateArgs, int) {
var cfg ValidateArgs
flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) }
flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
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
return &cfg, 1
}
cfg.Path = args[0]
return &cfg, 0
}
func (c *ValidateCommand) RunContext(ctx context.Context, cla *ValidateArgs) int {
// Parse the template
tpl, err := template.ParseFile(args[0])
tpl, err := template.ParseFile(cla.Path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
return 1
}
// If we're only checking syntax, then we're done already
if cfgSyntaxOnly {
if cla.SyntaxOnly {
c.Ui.Say("Syntax-only check passed. Everything looks okay.")
return 0
}
// Get the core
core, err := c.Meta.Core(tpl)
core, err := c.Meta.Core(tpl, &cla.MetaArgs)
if err != nil {
c.Ui.Error(err.Error())
return 1
@ -57,7 +75,7 @@ func (c *ValidateCommand) Run(args []string) int {
warnings := make(map[string][]string)
// Get the builds we care about
buildNames := c.Meta.BuildNames(core)
buildNames := core.BuildNames(c.CoreConfig.Only, c.CoreConfig.Except)
builds := make([]packer.Build, 0, len(buildNames))
for _, n := range buildNames {
b, err := core.Build(n)

View File

@ -59,7 +59,7 @@ func testParse(t *testing.T, tests []parseTest) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCfg, gotDiags := tt.parser.parse(tt.args.filename, tt.args.varFiles, tt.args.vars)
gotCfg, gotDiags := tt.parser.Parse(tt.args.filename, tt.args.varFiles, tt.args.vars)
if tt.parseWantDiags == (gotDiags == nil) {
t.Fatalf("Parser.parse() unexpected %q diagnostics.", gotDiags)
}
@ -68,6 +68,7 @@ func testParse(t *testing.T, tests []parseTest) {
}
if diff := cmp.Diff(tt.parseWantCfg, gotCfg,
cmpopts.IgnoreUnexported(
PackerConfig{},
cty.Value{},
cty.Type{},
Variable{},
@ -115,7 +116,7 @@ func testParse(t *testing.T, tests []parseTest) {
return
}
gotBuilds, gotDiags := tt.parser.getBuilds(gotCfg, nil, nil)
gotBuilds, gotDiags := gotCfg.GetBuilds(packer.GetBuildsOptions{})
if tt.getBuildsWantDiags == (gotDiags == nil) {
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
}

View File

@ -52,7 +52,15 @@ const (
hcl2VarJsonFileExt = ".auto.pkrvars.json"
)
func (p *Parser) parse(filename string, varFiles []string, argVars map[string]string) (*PackerConfig, hcl.Diagnostics) {
// Parse will Parse all HCL files in filename. Path can be a folder or a file.
//
// Parse will first Parse variables and then the rest; so that interpolation
// can happen.
//
// Parse returns a PackerConfig that contains configuration layout of a packer
// build; sources(builders)/provisioners/posts-processors will not be started
// and their contents wont be verified; Most syntax errors will cause an error.
func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]string) (*PackerConfig, hcl.Diagnostics) {
var files []*hcl.File
var diags hcl.Diagnostics
@ -89,7 +97,10 @@ func (p *Parser) parse(filename string, varFiles []string, argVars map[string]st
basedir = filepath.Dir(basedir)
}
cfg := &PackerConfig{
Basedir: basedir,
Basedir: basedir,
builderSchemas: p.BuilderSchemas,
provisionersSchemas: p.ProvisionersSchemas,
postProcessorsSchemas: p.PostProcessorsSchemas,
}
// Decode variable blocks so that they are available later on. Here locals

View File

@ -49,11 +49,11 @@ func (p *Parser) decodePostProcessor(block *hcl.Block) (*PostProcessorBlock, hcl
return postProcessor, diags
}
func (p *Parser) startPostProcessor(source *SourceBlock, pp *PostProcessorBlock, ectx *hcl.EvalContext, generatedVars map[string]string) (packer.PostProcessor, hcl.Diagnostics) {
func (cfg *PackerConfig) startPostProcessor(source *SourceBlock, pp *PostProcessorBlock, ectx *hcl.EvalContext, generatedVars map[string]string) (packer.PostProcessor, hcl.Diagnostics) {
// ProvisionerBlock represents a detected but unparsed provisioner
var diags hcl.Diagnostics
postProcessor, err := p.PostProcessorsSchemas.Start(pp.PType)
postProcessor, err := cfg.postProcessorsSchemas.Start(pp.PType)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Failed loading %s", pp.PType),

View File

@ -77,10 +77,10 @@ func (p *Parser) decodeProvisioner(block *hcl.Block) (*ProvisionerBlock, hcl.Dia
return provisioner, diags
}
func (p *Parser) startProvisioner(source *SourceBlock, pb *ProvisionerBlock, ectx *hcl.EvalContext, generatedVars map[string]string) (packer.Provisioner, hcl.Diagnostics) {
func (cfg *PackerConfig) startProvisioner(source *SourceBlock, pb *ProvisionerBlock, ectx *hcl.EvalContext, generatedVars map[string]string) (packer.Provisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics
provisioner, err := p.ProvisionersSchemas.Start(pb.PType)
provisioner, err := cfg.provisionersSchemas.Start(pb.PType)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("failed loading %s", pb.PType),

View File

@ -7,13 +7,13 @@ import (
"github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/packer"
"github.com/zclconf/go-cty/cty"
"github.com/gobwas/glob"
)
// PackerConfig represents a loaded Packer HCL config. It will contain
// references to all possible blocks of the allowed configuration.
type PackerConfig struct {
// parser *Parser
// Directory where the config files are defined
Basedir string
@ -31,6 +31,12 @@ type PackerConfig struct {
// Builds is the list of Build blocks defined in the config files.
Builds Builds
builderSchemas packer.BuilderStore
provisionersSchemas packer.ProvisionerStore
postProcessorsSchemas packer.PostProcessorStore
}
type ValidationOptions struct {
@ -188,11 +194,11 @@ func (c *PackerConfig) evaluateLocalVariable(local *Local) hcl.Diagnostics {
// getCoreBuildProvisioners takes a list of provisioner block, starts according
// provisioners and sends parsed HCL2 over to it.
func (p *Parser) getCoreBuildProvisioners(source *SourceBlock, blocks []*ProvisionerBlock, ectx *hcl.EvalContext, generatedVars map[string]string) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) {
func (cfg *PackerConfig) getCoreBuildProvisioners(source *SourceBlock, blocks []*ProvisionerBlock, ectx *hcl.EvalContext, generatedVars map[string]string) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics
res := []packer.CoreBuildProvisioner{}
for _, pb := range blocks {
provisioner, moreDiags := p.startProvisioner(source, pb, ectx, generatedVars)
provisioner, moreDiags := cfg.startProvisioner(source, pb, ectx, generatedVars)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
@ -228,11 +234,11 @@ func (p *Parser) getCoreBuildProvisioners(source *SourceBlock, blocks []*Provisi
// getCoreBuildProvisioners takes a list of post processor block, starts
// according provisioners and sends parsed HCL2 over to it.
func (p *Parser) getCoreBuildPostProcessors(source *SourceBlock, blocks []*PostProcessorBlock, ectx *hcl.EvalContext, generatedVars map[string]string) ([]packer.CoreBuildPostProcessor, hcl.Diagnostics) {
func (cfg *PackerConfig) getCoreBuildPostProcessors(source *SourceBlock, blocks []*PostProcessorBlock, ectx *hcl.EvalContext, generatedVars map[string]string) ([]packer.CoreBuildPostProcessor, hcl.Diagnostics) {
var diags hcl.Diagnostics
res := []packer.CoreBuildPostProcessor{}
for _, ppb := range blocks {
postProcessor, moreDiags := p.startPostProcessor(source, ppb, ectx, generatedVars)
postProcessor, moreDiags := cfg.startPostProcessor(source, ppb, ectx, generatedVars)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
@ -247,10 +253,10 @@ func (p *Parser) getCoreBuildPostProcessors(source *SourceBlock, blocks []*PostP
return res, diags
}
// getBuilds will return a list of packer Build based on the HCL2 parsed build
// GetBuilds returns a list of packer Build based on the HCL2 parsed build
// blocks. All Builders, Provisioners and Post Processors will be started and
// configured.
func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs []glob.Glob) ([]packer.Build, hcl.Diagnostics) {
func (cfg *PackerConfig) GetBuilds(opts packer.GetBuildsOptions) ([]packer.Build, hcl.Diagnostics) {
res := []packer.Build{}
var diags hcl.Diagnostics
@ -270,7 +276,11 @@ func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs
buildName := fmt.Sprintf("%s.%s", src.Type, src.Name)
// -only
if len(onlyGlobs) > 0 {
if len(opts.Only) > 0 {
onlyGlobs, diags := convertFilterOption(opts.Only, "only")
if diags.HasErrors() {
return nil, diags
}
include := false
for _, onlyGlob := range onlyGlobs {
if onlyGlob.Match(buildName) {
@ -284,7 +294,11 @@ func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs
}
// -except
if len(exceptGlobs) > 0 {
if len(opts.Except) > 0 {
exceptGlobs, diags := convertFilterOption(opts.Except, "except")
if diags.HasErrors() {
return nil, diags
}
exclude := false
for _, exceptGlob := range exceptGlobs {
if exceptGlob.Match(buildName) {
@ -297,7 +311,7 @@ func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs
}
}
builder, moreDiags, generatedVars := p.startBuilder(src, cfg.EvalContext(nil))
builder, moreDiags, generatedVars := cfg.startBuilder(src, cfg.EvalContext(nil))
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
@ -323,12 +337,12 @@ func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs
}
}
provisioners, moreDiags := p.getCoreBuildProvisioners(src, build.ProvisionerBlocks, cfg.EvalContext(variables), generatedPlaceholderMap)
provisioners, moreDiags := cfg.getCoreBuildProvisioners(src, build.ProvisionerBlocks, cfg.EvalContext(variables), generatedPlaceholderMap)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
}
postProcessors, moreDiags := p.getCoreBuildPostProcessors(src, build.PostProcessors, cfg.EvalContext(variables), generatedPlaceholderMap)
postProcessors, moreDiags := cfg.getCoreBuildPostProcessors(src, build.PostProcessors, cfg.EvalContext(variables), generatedPlaceholderMap)
pps := [][]packer.CoreBuildPostProcessor{}
if len(postProcessors) > 0 {
pps = [][]packer.CoreBuildPostProcessor{postProcessors}
@ -350,60 +364,3 @@ func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs
}
return res, diags
}
// Convert -only and -except globs to glob.Glob instances.
func convertFilterOption(patterns []string, optionName string) ([]glob.Glob, hcl.Diagnostics) {
var globs []glob.Glob
var diags hcl.Diagnostics
for _, pattern := range patterns {
g, err := glob.Compile(pattern)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Invalid -%s pattern %s: %s", optionName, pattern, err),
Severity: hcl.DiagError,
})
}
globs = append(globs, g)
}
return globs, diags
}
// Parse will parse HCL file(s) in path. Path can be a folder or a file.
//
// Parse will first parse variables and then the rest; so that interpolation
// can happen.
//
// For each build block a packer.Build will be started, and for each builder,
// all provisioners and post-processors will be started.
//
// Parse then return a slice of packer.Builds; which are what packer core uses
// to run builds.
func (p *Parser) Parse(path string, varFiles []string, argVars map[string]string, onlyBuilds []string, exceptBuilds []string) ([]packer.Build, hcl.Diagnostics) {
var onlyGlobs []glob.Glob
if len(onlyBuilds) > 0 {
og, diags := convertFilterOption(onlyBuilds, "only")
if diags.HasErrors() {
return nil, diags
}
onlyGlobs = og
}
var exceptGlobs []glob.Glob
if len(exceptBuilds) > 0 {
eg, diags := convertFilterOption(exceptBuilds, "except")
if diags.HasErrors() {
return nil, diags
}
exceptGlobs = eg
}
cfg, diags := p.parse(path, varFiles, argVars)
if diags.HasErrors() {
return nil, diags
}
builds, moreDiags := p.getBuilds(cfg, onlyGlobs, exceptGlobs)
return builds, append(diags, moreDiags...)
}

View File

@ -38,10 +38,10 @@ func (p *Parser) decodeSource(block *hcl.Block) (*SourceBlock, hcl.Diagnostics)
return source, diags
}
func (p *Parser) startBuilder(source *SourceBlock, ectx *hcl.EvalContext) (packer.Builder, hcl.Diagnostics, []string) {
func (cfg *PackerConfig) startBuilder(source *SourceBlock, ectx *hcl.EvalContext) (packer.Builder, hcl.Diagnostics, []string) {
var diags hcl.Diagnostics
builder, err := p.BuilderSchemas.Start(source.Type)
builder, err := cfg.builderSchemas.Start(source.Type)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: "Failed to load " + sourceLabel + " type",

View File

@ -1,11 +1,13 @@
package hcl2template
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/gobwas/glob"
"github.com/hashicorp/hcl/v2"
)
@ -88,3 +90,22 @@ func GetHCL2Files(filename, hclSuffix, jsonSuffix string) (hclFiles, jsonFiles [
return hclFiles, jsonFiles, diags
}
// Convert -only and -except globs to glob.Glob instances.
func convertFilterOption(patterns []string, optionName string) ([]glob.Glob, hcl.Diagnostics) {
var globs []glob.Glob
var diags hcl.Diagnostics
for _, pattern := range patterns {
g, err := glob.Compile(pattern)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Invalid -%s pattern %s: %s", optionName, pattern, err),
Severity: hcl.DiagError,
})
}
globs = append(globs, g)
}
return globs, diags
}

View File

@ -1,156 +0,0 @@
package hcl2template
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
func translateBuilder(path string) (string, error) {
type ConfigV1 map[string]json.RawMessage
type ConfigV1V2 struct {
Artifact map[string]map[string]json.RawMessage `json:"artifact"`
}
type Type struct {
Name string `json:"name"`
Type string `json:"type"`
}
type PostProcessor struct {
Type string `json:"type"`
Except []string `json:"except"`
Only []string `json:"only"`
}
b, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
c1 := ConfigV1{}
if err := json.Unmarshal(b, &c1); err != nil {
return "", err
}
c12 := ConfigV1V2{}
if err := json.Unmarshal(b, &c12); err != nil {
return "", err
}
rawBuilder, found := c1["builders"]
if !found {
// no v1 builders
return path, nil
}
var tn []Type
if err := json.Unmarshal([]byte(rawBuilder), &tn); err != nil {
return "", err
}
var rawbuilders []json.RawMessage
if err := json.Unmarshal([]byte(rawBuilder), &rawbuilders); err != nil {
return "", err
}
var typePPs []PostProcessor
var rawPPs []json.RawMessage
if rawPP := c1["post-processors"]; len(rawPP) != 0 {
if err := json.Unmarshal([]byte(rawPP), &typePPs); err != nil {
return "", err
}
if err := json.Unmarshal([]byte(rawPP), &rawPPs); err != nil {
return "", err
}
}
for n, tn := range tn {
builderName := tn.Type
if tn.Name != "" {
builderName = tn.Name
}
if c12.Artifact[tn.Type] == nil {
c12.Artifact[tn.Type] = map[string]json.RawMessage{}
}
name := tn.Name
if name == "" {
name = fmt.Sprintf("autotranslated-builder-%d", len(c12.Artifact[tn.Type]))
}
if _, exists := c12.Artifact[tn.Type][name]; exists {
return "", fmt.Errorf("%s-%s is defined in old and new config", tn.Type, name)
}
rawbuilder := rawbuilders[n]
rawbuilder = removeKey(rawbuilder, "name", "only", "type")
c12.Artifact[tn.Type][name] = rawbuilder
for n, pp := range typePPs {
skip := false
for _, except := range pp.Except {
if except == builderName {
skip = true
break
}
}
for _, only := range pp.Only {
if only != builderName {
skip = true
break
}
}
if skip {
continue
}
if c12.Artifact[pp.Type] == nil {
c12.Artifact[pp.Type] = map[string]json.RawMessage{}
}
name := fmt.Sprintf("autotranslated-post-processor-%d", len(c12.Artifact[pp.Type]))
if _, exists := c12.Artifact[tn.Type][name]; exists {
return "", fmt.Errorf("%s-%s is defined in old and new config", tn.Type, name)
}
rawpp := rawPPs[n]
rawpp = rawpp[:len(rawpp)-1]
rawpp = append(rawpp, json.RawMessage(`,"source":"$artifacts.`+tn.Type+`.`+builderName+`"}`)...)
rawpp = removeKey(rawpp, "name", "only", "type")
c12.Artifact[pp.Type][name] = rawpp
log.Printf("%s", rawpp)
}
}
path = strings.TrimSuffix(path, ".json")
path = strings.TrimSuffix(path, ".pk")
path = path + ".v2.pk.json"
file, err := os.Create(path)
if err != nil {
return "", err
}
defer file.Close()
enc := json.NewEncoder(file)
enc.SetIndent("", " ")
return path, enc.Encode(c12)
}
func removeKey(in json.RawMessage, keys ...string) json.RawMessage {
m := map[string]json.RawMessage{}
if err := json.Unmarshal(in, &m); err != nil {
panic(err)
}
for _, key := range keys {
delete(m, key)
}
b, err := json.Marshal(m)
if err != nil {
panic(err)
}
return b
}

View File

@ -11,6 +11,7 @@ import (
multierror "github.com/hashicorp/go-multierror"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/template"
"github.com/hashicorp/packer/template/interpolate"
)
@ -126,9 +127,22 @@ func NewCore(c *CoreConfig) (*Core, error) {
}
// BuildNames returns the builds that are available in this configured core.
func (c *Core) BuildNames() []string {
func (c *Core) BuildNames(only, except []string) []string {
sort.Strings(only)
sort.Strings(except)
r := make([]string, 0, len(c.builds))
for n := range c.builds {
onlyPos := sort.SearchStrings(only, n)
foundInOnly := onlyPos < len(only) && only[onlyPos] == n
if len(only) > 0 && !foundInOnly {
continue
}
if pos := sort.SearchStrings(except, n); pos < len(except) && except[pos] == n {
continue
}
r = append(r, n)
}
sort.Strings(r)
@ -185,6 +199,25 @@ func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName
return cbp, nil
}
func (c *Core) GetBuilds(opts GetBuildsOptions) ([]Build, hcl.Diagnostics) {
buildNames := c.BuildNames(opts.Only, opts.Except)
builds := []Build{}
diags := hcl.Diagnostics{}
for _, n := range buildNames {
b, err := c.Build(n)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to initialize build %q", n),
Detail: err.Error(),
})
continue
}
builds = append(builds, b)
}
return builds, diags
}
// Build returns the Build object for the given name.
func (c *Core) Build(n string) (Build, error) {
// Setup the builder

View File

@ -45,7 +45,7 @@ func TestCoreBuildNames(t *testing.T) {
t.Fatalf("err: %s\n\n%s", tc.File, err)
}
names := core.BuildNames()
names := core.BuildNames(nil, nil)
if !reflect.DeepEqual(names, tc.Result) {
t.Fatalf("err: %s\n\n%#v", tc.File, names)
}

View File

@ -0,0 +1,51 @@
// Code generated by "enumer -type FixConfigMode"; DO NOT EDIT.
//
package packer
import (
"fmt"
)
const _FixConfigModeName = "StdoutInplaceDiff"
var _FixConfigModeIndex = [...]uint8{0, 6, 13, 17}
func (i FixConfigMode) String() string {
if i < 0 || i >= FixConfigMode(len(_FixConfigModeIndex)-1) {
return fmt.Sprintf("FixConfigMode(%d)", i)
}
return _FixConfigModeName[_FixConfigModeIndex[i]:_FixConfigModeIndex[i+1]]
}
var _FixConfigModeValues = []FixConfigMode{0, 1, 2}
var _FixConfigModeNameToValueMap = map[string]FixConfigMode{
_FixConfigModeName[0:6]: 0,
_FixConfigModeName[6:13]: 1,
_FixConfigModeName[13:17]: 2,
}
// FixConfigModeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func FixConfigModeString(s string) (FixConfigMode, error) {
if val, ok := _FixConfigModeNameToValueMap[s]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to FixConfigMode values", s)
}
// FixConfigModeValues returns all values of the enum
func FixConfigModeValues() []FixConfigMode {
return _FixConfigModeValues
}
// IsAFixConfigMode returns "true" if the value is listed in the enum definition. "false" otherwise
func (i FixConfigMode) IsAFixConfigMode() bool {
for _, v := range _FixConfigModeValues {
if i == v {
return true
}
}
return false
}

38
packer/run_interfaces.go Normal file
View File

@ -0,0 +1,38 @@
package packer
import "github.com/hashicorp/hcl/v2"
type GetBuildsOptions struct {
// Get builds except the ones that match with except and with only the ones
// that match with Only. When those are empty everything matches.
Except, Only []string
}
type BuildGetter interface {
// GetBuilds return all possible builds for a config. It also starts all
// builders.
// TODO(azr): rename to builder starter ?
GetBuilds(GetBuildsOptions) ([]Build, hcl.Diagnostics)
}
//go:generate enumer -type FixConfigMode
type FixConfigMode int
const (
// Stdout will make FixConfig simply print what the config should be; it
// will only work when a single file is passed.
Stdout FixConfigMode = iota
// Inplace fixes your files on the spot.
Inplace
// Diff shows a full diff.
Diff
)
type FixConfigOptions struct {
DiffOnly bool
}
type ConfigFixer interface {
// FixConfig will output the config in a fixed manner.
FixConfig(FixConfigOptions) hcl.Diagnostics
}

View File

@ -52,11 +52,6 @@ artifacts that are created will be outputted at the end of the build.
attribute is specified within the configuration. `-only` does not apply to
post-processors.
- `-parallel=false` - /!\ Deprecated, use `-parallel-builds=1` instead,
setting `-parallel-builds=N` to more that 0 will ignore the `-parallel`
setting. Set `-parallel=false` to disable parallelization of multiple
builders (on by default).
- `-parallel-builds=N` - Limit the number of builds to run in parallel, 0
means no limit (defaults to 0).