Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions acceptance.bats
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,27 @@
[ "${lines[1]}" = "1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions" ]
}

@test "Test command with namespace exact match" {
run ./conftest test -p examples/docker/policy examples/docker/Dockerfile --namespace 'main'
[ "$status" -eq 1 ]
[[ "$output" =~ "unallowed image found" ]]
[[ ! "$output" =~ "unallowed commands found" ]]
}

@test "Test command with namespace wildcard matching all" {
run ./conftest test -p examples/docker/policy examples/docker/Dockerfile --namespace '*'
[ "$status" -eq 1 ]
[[ "$output" =~ "unallowed image found" ]]
[[ "$output" =~ "unallowed commands found" ]]
}

@test "Test command with namespace wildcard prefix pattern" {
run ./conftest test -p examples/docker/policy examples/docker/Dockerfile --namespace 'c*'
[ "$status" -eq 1 ]
[[ ! "$output" =~ "unallowed image found" ]]
[[ "$output" =~ "unallowed commands found" ]]
}

@test "Verify command has trace flag" {
run ./conftest verify --policy ./examples/kubernetes/policy --trace
[ "$status" -eq 0 ]
Expand Down
37 changes: 37 additions & 0 deletions runner/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/open-policy-agent/conftest/downloader"
"github.com/open-policy-agent/conftest/output"
Expand Down Expand Up @@ -89,6 +91,8 @@ func (t *TestRunner) Run(ctx context.Context, fileList []string) (output.CheckRe
namespaces := t.Namespace
if t.AllNamespaces {
namespaces = engine.Namespaces()
} else if hasWildcard(t.Namespace) {
namespaces = filterNamespaces(engine.Namespaces(), t.Namespace)
}

var results output.CheckResults
Expand Down Expand Up @@ -211,3 +215,36 @@ func getFilesFromDirectory(directory string, ignoreRegex string) ([]string, erro

return files, nil
}

// hasWildcard checks if any of the given patterns contain wildcard characters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a note that this is based on what path.Match() supports.

func hasWildcard(patterns []string) bool {
for _, pattern := range patterns {
if strings.ContainsAny(pattern, "*?[") {
return true
}
}
return false
}

// filterNamespaces filters the available namespaces using the given patterns.
// Patterns support glob-style matching with *, ?, and [...] syntax.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a note that this is based on what path.Match() supports.

func filterNamespaces(available []string, patterns []string) []string {
seen := make(map[string]bool)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary. engine.Namespaces() returns the unique list already.

var result []string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Call this namespaces rather than a generic variable name.

for _, pattern := range patterns {
for _, ns := range available {
if seen[ns] {
continue
}
matched, err := path.Match(pattern, ns)
if err != nil {
continue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should raise an error rather than silently continuing.

}
if matched {
seen[ns] = true
result = append(result, ns)
}
}
}
return result
}
138 changes: 138 additions & 0 deletions runner/test_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package runner

import (
"reflect"
"testing"
)

func TestHasWildcard(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add t.Parallel().

tests := []struct {
name string
patterns []string
want bool
}{
{
name: "no wildcard",
patterns: []string{"main", "test"},
want: false,
},
{
name: "asterisk wildcard",
patterns: []string{"main.*"},
want: true,
},
{
name: "question mark wildcard",
patterns: []string{"main.?"},
want: true,
},
{
name: "bracket wildcard",
patterns: []string{"main.[abc]"},
want: true,
},
{
name: "mixed patterns",
patterns: []string{"main", "test.*"},
want: true,
},
{
name: "empty patterns",
patterns: []string{},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add t.Parallel().

if got := hasWildcard(tt.patterns); got != tt.want {
t.Errorf("hasWildcard() = %v, want %v", got, tt.want)
}
})
}
}

func TestFilterNamespaces(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add t.Parallel().

tests := []struct {
name string
available []string
patterns []string
want []string
}{
{
name: "asterisk matches suffix",
available: []string{"main", "main.gke", "main.aws", "test"},
patterns: []string{"main.*"},
want: []string{"main.gke", "main.aws"},
},
{
name: "asterisk matches prefix",
available: []string{"main.gke", "test.gke", "main.aws"},
patterns: []string{"*.gke"},
want: []string{"main.gke", "test.gke"},
},
{
name: "exact match without wildcard",
available: []string{"main", "main.gke"},
patterns: []string{"main"},
want: []string{"main"},
},
{
name: "multiple patterns",
available: []string{"main", "main.gke", "test", "test.aws"},
patterns: []string{"main.*", "test.*"},
want: []string{"main.gke", "test.aws"},
},
{
name: "no matches",
available: []string{"main", "test"},
patterns: []string{"foo.*"},
want: nil,
},
{
name: "match all with star",
available: []string{"main", "test", "foo"},
patterns: []string{"*"},
want: []string{"main", "test", "foo"},
},
{
name: "question mark pattern",
available: []string{"main.a", "main.b", "main.ab"},
patterns: []string{"main.?"},
want: []string{"main.a", "main.b"},
},
{
name: "bracket pattern",
available: []string{"main.a", "main.b", "main.c"},
patterns: []string{"main.[ab]"},
want: []string{"main.a", "main.b"},
},
{
name: "deduplicate matches",
available: []string{"main.gke"},
patterns: []string{"main.*", "*.gke"},
want: []string{"main.gke"},
},
{
name: "empty available",
available: []string{},
patterns: []string{"main.*"},
want: nil,
},
{
name: "empty patterns",
available: []string{"main", "test"},
patterns: []string{},
want: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add t.Parallel().

got := filterNamespaces(tt.available, tt.patterns)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("filterNamespaces() = %v, want %v", got, tt.want)
}
})
}
}