Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup: Use generics to simplify code for parse_config and related builtins #857

Merged
merged 1 commit into from
Aug 31, 2023
Merged
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
78 changes: 40 additions & 38 deletions builtins/parse_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func registerParseConfig() {
Name: "parse_config",
Decl: types.NewFunction(
types.Args(types.S, types.S), // parser name, configuration
types.NewObject(nil, types.NewDynamicProperty(types.S, types.NewAny())), // map[string]interface{} aka JSON
types.NewObject(nil, types.NewDynamicProperty(types.S, types.NewAny())), // map[string]any aka JSON
),
}
rego.RegisterBuiltin2(&decl, parseConfig)
Expand All @@ -35,7 +35,7 @@ func registerParseConfigFile() {
Name: "parse_config_file",
Decl: types.NewFunction(
types.Args(types.S), // path to configuration file
types.NewObject(nil, types.NewDynamicProperty(types.S, types.NewAny())), // map[string]interface{} aka JSON
types.NewObject(nil, types.NewDynamicProperty(types.S, types.NewAny())), // map[string]any aka JSON
),
}
rego.RegisterBuiltin1(&decl, parseConfigFile)
Expand All @@ -46,7 +46,7 @@ func registerParseCombinedConfigFiles() {
Name: "parse_combined_config_files",
Decl: types.NewFunction(
types.Args(types.NewArray(nil, types.S)), // paths to configuration files
types.NewObject(nil, types.NewDynamicProperty(types.S, types.NewAny())), // map[string]interface{} aka JSON
types.NewObject(nil, types.NewDynamicProperty(types.S, types.NewAny())), // map[string]any aka JSON
),
}
rego.RegisterBuiltin1(&decl, parseCombinedConfigFiles)
Expand All @@ -56,24 +56,17 @@ func registerParseCombinedConfigFiles() {
// parsed configuration as a Rego object. This can be used to parse all of the
// configuration formats conftest supports in-line in Rego policies.
func parseConfig(bctx rego.BuiltinContext, op1, op2 *ast.Term) (*ast.Term, error) {
args, err := decodeArgs([]*ast.Term{op1, op2})
args, err := decodeTypedArgs("", op1, op2)
if err != nil {
return nil, fmt.Errorf("decode args: %w", err)
}
parserName, ok := args[0].(string)
if !ok {
return nil, fmt.Errorf("parser name %v [%T] is not expected type string", args[0], args[0])
}
config, ok := args[1].(string)
if !ok {
return nil, fmt.Errorf("config %v [%T] is not expected type string", args[1], args[1])
}
parserName, config := args[0], args[1]

parser, err := parser.New(parserName)
if err != nil {
return nil, fmt.Errorf("create config parser: %w", err)
}

var cfg map[string]interface{}
var cfg map[string]any
if err := parser.Unmarshal([]byte(config), &cfg); err != nil {
return nil, fmt.Errorf("unmarshal config: %w", err)
}
Expand All @@ -84,15 +77,12 @@ func parseConfig(bctx rego.BuiltinContext, op1, op2 *ast.Term) (*ast.Term, error
// parseConfigFile takes a config file path, parses the config file, and
// returns the parsed configuration as a Rego object.
func parseConfigFile(bctx rego.BuiltinContext, op1 *ast.Term) (*ast.Term, error) {
args, err := decodeArgs([]*ast.Term{op1})
args, err := decodeTypedArgs("", op1)
if err != nil {
return nil, fmt.Errorf("decode args: %w", err)
}
file, ok := args[0].(string)
if !ok {
return nil, fmt.Errorf("file %v [%T] is not expected type string", args[0], args[0])
}
filePath := filepath.Join(filepath.Dir(bctx.Location.File), file)
filePath := filepath.Join(filepath.Dir(bctx.Location.File), args[0])

parser, err := parser.NewFromPath(filePath)
if err != nil {
return nil, fmt.Errorf("create config parser: %w", err)
Expand All @@ -102,54 +92,66 @@ func parseConfigFile(bctx rego.BuiltinContext, op1 *ast.Term) (*ast.Term, error)
return nil, fmt.Errorf("read config file %s: %w", filePath, err)
}

var cfg map[string]interface{}
var cfg map[string]any
if err := parser.Unmarshal(contents, &cfg); err != nil {
return nil, fmt.Errorf("unmarshal config: %w", err)
}

return toAST(bctx, cfg, contents)
}

// parseCombinedConfigFiles
// parseCombinedConfigFiles takes multiple config file paths, parses the configs,
// combines them, and returns that as a Rego object.
func parseCombinedConfigFiles(bctx rego.BuiltinContext, op1 *ast.Term) (*ast.Term, error) {
args, err := decodeArgs([]*ast.Term{op1})
iface, err := ast.ValueToInterface(op1.Value, nil)
if err != nil {
return nil, fmt.Errorf("decode args: %w", err)
return nil, fmt.Errorf("ast.ValueToInterface: %w", err)
}
slice, ok := iface.([]any)
if !ok {
return nil, fmt.Errorf("argument is not a slice")
}

filePaths := []string{}
fileList := args[0].([]interface{})
for _, file := range fileList {
filePaths = append(filePaths, filepath.Join(filepath.Dir(bctx.Location.File), file.(string)))
var paths []string
for i, s := range slice {
path, ok := s.(string)
if !ok {
return nil, fmt.Errorf("index %d is not expected type string", i)
}
paths = append(paths, filepath.Join(filepath.Dir(bctx.Location.File), path))
}

cfg, err := parser.ParseConfigurations(filePaths)
cfg, err := parser.ParseConfigurations(paths)
if err != nil {
return nil, fmt.Errorf("failed to parse combine configurations: %w", err)
return nil, fmt.Errorf("parse combine configurations: %w", err)
}
combinedCfg := parser.CombineConfigurations(cfg)
combinedContent, err := json.Marshal(combinedCfg)
combined := parser.CombineConfigurations(cfg)
content, err := json.Marshal(combined)
if err != nil {
return nil, fmt.Errorf("failed to marshal combined content: %w", err)
return nil, fmt.Errorf("marshal combined content: %w", err)
}

return toAST(bctx, combinedCfg["Combined"], combinedContent)
return toAST(bctx, combined["Combined"], content)
}

func decodeArgs(args []*ast.Term) ([]interface{}, error) {
decoded := make([]interface{}, len(args))
func decodeTypedArgs[T any](ty T, args ...*ast.Term) ([]T, error) {
decoded := make([]T, len(args))
for i, arg := range args {
iface, err := ast.ValueToInterface(arg.Value, nil)
if err != nil {
return nil, fmt.Errorf("ast.ValueToInterface: %w", err)
}
decoded[i] = iface
v, ok := iface.(T)
if !ok {
return nil, fmt.Errorf("argument %d is not type %T, have %T", i, ty, iface)
}
decoded[i] = v
}

return decoded, nil
}

func toAST(bctx rego.BuiltinContext, cfg interface{}, contents []byte) (*ast.Term, error) {
func toAST(bctx rego.BuiltinContext, cfg any, contents []byte) (*ast.Term, error) {
val, err := ast.InterfaceToValue(cfg)
if err != nil {
return nil, fmt.Errorf("convert config to ast.Value: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion examples/kubernetes/combine/combine.rego
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ service_selects_app(app) {
input[i].contents.kind == "Service"
service := input[i].contents
service.spec.selector.app == app
}
}
2 changes: 2 additions & 0 deletions parser/cyclonedx/cyclonedx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestCycloneDXParserValid(t *testing.T) {
t.Error("There should be information parsed but its nil")
}

//#nosec until https://github.com/securego/gosec/issues/1001 is fixed
expectedSHA256 := "sha256:d7ec60cf8390612b360c857688b383068b580d9a6ab78417c9493170ad3f1616"

metadata := input.(map[string]interface{})["metadata"]
Expand Down Expand Up @@ -103,6 +104,7 @@ func TestCycloneDXParserInValid(t *testing.T) {
t.Error("There should be information parsed but its nil")
}

//#nosec until https://github.com/securego/gosec/issues/1001 is fixed
expectedSHA256 := "sha256:d7ec60cf8390612b360c857688b383068b580d9a6ab78417c9493170ad3f1616"

metadata := input.(map[string]interface{})["metadata"]
Expand Down