teach HCL mode builds to honor -only and -except options (#8947)
This commit is contained in:
parent
e4df3b262b
commit
c0a6623ea2
|
@ -104,7 +104,7 @@ func (c *BuildCommand) GetBuildsFromHCL(path string) ([]packer.Build, int) {
|
|||
PostProcessorsSchemas: c.CoreConfig.Components.PostProcessorStore,
|
||||
}
|
||||
|
||||
builds, diags := parser.Parse(path, c.varFiles, c.flagVars)
|
||||
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)
|
||||
|
|
|
@ -394,6 +394,88 @@ func TestBuildExceptFileCommaFlags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testHCLOnlyExceptFlags(t *testing.T, args, present, notPresent []string) {
|
||||
c := &BuildCommand{
|
||||
Meta: testMetaFile(t),
|
||||
}
|
||||
|
||||
defer cleanup()
|
||||
|
||||
finalArgs := []string{"-parallel=false"}
|
||||
finalArgs = append(finalArgs, args...)
|
||||
finalArgs = append(finalArgs, testFixture("hcl-only-except"))
|
||||
|
||||
if code := c.Run(finalArgs); code != 0 {
|
||||
fatalCommand(t, c.Meta)
|
||||
}
|
||||
|
||||
for _, f := range notPresent {
|
||||
if fileExists(f) {
|
||||
t.Errorf("Expected NOT to find %s", f)
|
||||
}
|
||||
}
|
||||
for _, f := range present {
|
||||
if !fileExists(f) {
|
||||
t.Errorf("Expected to find %s", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCommand_HCLOnlyExceptOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
args []string
|
||||
present []string
|
||||
notPresent []string
|
||||
}{
|
||||
{
|
||||
[]string{"-only=chocolate"},
|
||||
[]string{},
|
||||
[]string{"chocolate.txt", "vanilla.txt", "cherry.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-only=*chocolate*"},
|
||||
[]string{"chocolate.txt"},
|
||||
[]string{"vanilla.txt", "cherry.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-except=*chocolate*"},
|
||||
[]string{"vanilla.txt", "cherry.txt"},
|
||||
[]string{"chocolate.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-except=*ch*"},
|
||||
[]string{"vanilla.txt"},
|
||||
[]string{"chocolate.txt", "cherry.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-only=*chocolate*", "-only=*vanilla*"},
|
||||
[]string{"chocolate.txt", "vanilla.txt"},
|
||||
[]string{"cherry.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-except=*chocolate*", "-except=*vanilla*"},
|
||||
[]string{"cherry.txt"},
|
||||
[]string{"chocolate.txt", "vanilla.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-only=file.chocolate"},
|
||||
[]string{"chocolate.txt"},
|
||||
[]string{"vanilla.txt", "cherry.txt"},
|
||||
},
|
||||
{
|
||||
[]string{"-except=file.chocolate"},
|
||||
[]string{"vanilla.txt", "cherry.txt"},
|
||||
[]string{"chocolate.txt"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%s", tt.args), func(t *testing.T) {
|
||||
testHCLOnlyExceptFlags(t, tt.args, tt.present, tt.notPresent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWithNonExistingBuilder(t *testing.T) {
|
||||
c := &BuildCommand{
|
||||
Meta: testMetaFile(t),
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
source "file" "chocolate" {
|
||||
content = "chocolate"
|
||||
target = "chocolate.txt"
|
||||
}
|
||||
|
||||
source "file" "vanilla" {
|
||||
content = "vanilla"
|
||||
target = "vanilla.txt"
|
||||
}
|
||||
|
||||
source "file" "cherry" {
|
||||
content = "cherry"
|
||||
target = "cherry.txt"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"file.chocolate",
|
||||
"file.vanilla",
|
||||
"file.cherry",
|
||||
]
|
||||
}
|
2
go.mod
2
go.mod
|
@ -51,7 +51,7 @@ require (
|
|||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-ini/ini v1.25.4
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gocolly/colly v1.2.0
|
||||
github.com/gofrs/flock v0.7.1
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
|
|
|
@ -115,7 +115,7 @@ func testParse(t *testing.T, tests []parseTest) {
|
|||
return
|
||||
}
|
||||
|
||||
gotBuilds, gotDiags := tt.parser.getBuilds(gotCfg)
|
||||
gotBuilds, gotDiags := tt.parser.getBuilds(gotCfg, nil, nil)
|
||||
if tt.getBuildsWantDiags == (gotDiags == nil) {
|
||||
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ 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
|
||||
|
@ -248,7 +250,7 @@ func (p *Parser) getCoreBuildPostProcessors(source *SourceBlock, blocks []*PostP
|
|||
// getBuilds will return 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) ([]packer.Build, hcl.Diagnostics) {
|
||||
func (p *Parser) getBuilds(cfg *PackerConfig, onlyGlobs []glob.Glob, exceptGlobs []glob.Glob) ([]packer.Build, hcl.Diagnostics) {
|
||||
res := []packer.Build{}
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
|
@ -263,6 +265,38 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
|||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply the -only and -except command-line options to exclude matching builds.
|
||||
buildName := fmt.Sprintf("%s.%s", src.Type, src.Name)
|
||||
|
||||
// -only
|
||||
if len(onlyGlobs) > 0 {
|
||||
include := false
|
||||
for _, onlyGlob := range onlyGlobs {
|
||||
if onlyGlob.Match(buildName) {
|
||||
include = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !include {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// -except
|
||||
if len(exceptGlobs) > 0 {
|
||||
exclude := false
|
||||
for _, exceptGlob := range exceptGlobs {
|
||||
if exceptGlob.Match(buildName) {
|
||||
exclude = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if exclude {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
builder, moreDiags, generatedVars := p.startBuilder(src, cfg.EvalContext(nil))
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
|
@ -317,6 +351,25 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
|||
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
|
||||
|
@ -327,12 +380,30 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
|||
//
|
||||
// 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) ([]packer.Build, hcl.Diagnostics) {
|
||||
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)
|
||||
builds, moreDiags := p.getBuilds(cfg, onlyGlobs, exceptGlobs)
|
||||
return builds, append(diags, moreDiags...)
|
||||
}
|
||||
|
|
|
@ -262,3 +262,25 @@ func TestParser_complete(t *testing.T) {
|
|||
}
|
||||
testParse(t, tests)
|
||||
}
|
||||
|
||||
func TestParser_ValidateFilterOption(t *testing.T) {
|
||||
tests := []struct {
|
||||
pattern string
|
||||
expectError bool
|
||||
}{
|
||||
{"*foo*", false},
|
||||
{"foo[]bar", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.pattern, func(t *testing.T) {
|
||||
_, diags := convertFilterOption([]string{test.pattern}, "")
|
||||
if diags.HasErrors() && !test.expectError {
|
||||
t.Fatalf("Expected %s to parse as glob", test.pattern)
|
||||
}
|
||||
if !diags.HasErrors() && test.expectError {
|
||||
t.Fatalf("Expected %s to fail to parse as glob", test.pattern)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue