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,
|
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.
|
// write HCL errors/diagnostics if any.
|
||||||
b := bytes.NewBuffer(nil)
|
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) {
|
func TestBuildWithNonExistingBuilder(t *testing.T) {
|
||||||
c := &BuildCommand{
|
c := &BuildCommand{
|
||||||
Meta: testMetaFile(t),
|
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/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-ini/ini v1.25.4
|
github.com/go-ini/ini v1.25.4
|
||||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
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/gocolly/colly v1.2.0
|
||||||
github.com/gofrs/flock v0.7.1
|
github.com/gofrs/flock v0.7.1
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||||
|
|
|
@ -115,7 +115,7 @@ func testParse(t *testing.T, tests []parseTest) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gotBuilds, gotDiags := tt.parser.getBuilds(gotCfg)
|
gotBuilds, gotDiags := tt.parser.getBuilds(gotCfg, nil, nil)
|
||||||
if tt.getBuildsWantDiags == (gotDiags == nil) {
|
if tt.getBuildsWantDiags == (gotDiags == nil) {
|
||||||
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
|
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"github.com/hashicorp/packer/helper/common"
|
"github.com/hashicorp/packer/helper/common"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PackerConfig represents a loaded Packer HCL config. It will contain
|
// 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
|
// 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
|
// blocks. All Builders, Provisioners and Post Processors will be started and
|
||||||
// configured.
|
// 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{}
|
res := []packer.Build{}
|
||||||
var diags hcl.Diagnostics
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
@ -263,6 +265,38 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
||||||
})
|
})
|
||||||
continue
|
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))
|
builder, moreDiags, generatedVars := p.startBuilder(src, cfg.EvalContext(nil))
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
if moreDiags.HasErrors() {
|
if moreDiags.HasErrors() {
|
||||||
|
@ -317,6 +351,25 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
||||||
return res, diags
|
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 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
|
// 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
|
// Parse then return a slice of packer.Builds; which are what packer core uses
|
||||||
// to run builds.
|
// 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)
|
cfg, diags := p.parse(path, varFiles, argVars)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
builds, moreDiags := p.getBuilds(cfg)
|
builds, moreDiags := p.getBuilds(cfg, onlyGlobs, exceptGlobs)
|
||||||
return builds, append(diags, moreDiags...)
|
return builds, append(diags, moreDiags...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,3 +262,25 @@ func TestParser_complete(t *testing.T) {
|
||||||
}
|
}
|
||||||
testParse(t, tests)
|
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