Skip to content

Commit

Permalink
cleanup: Use generics to simplify code for parse_config and related b…
Browse files Browse the repository at this point in the history
…uiltins (#857)

Signed-off-by: James Alseth <[email protected]>
  • Loading branch information
jalseth authored Aug 31, 2023
1 parent c7e9f05 commit fe4c225
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 39 deletions.
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

0 comments on commit fe4c225

Please sign in to comment.