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

Add support for getting line numbers for all attributes #1055

Merged
1 change: 1 addition & 0 deletions pkg/iac-providers/output/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type ResourceConfig struct {
Line int `json:"line"`
Type string `json:"type"`
Config interface{} `json:"config"`
LineConfig interface{} `json:"line_config,omitempty" yaml:"line_config,omitempty"`
// SkipRules will hold the rules to be skipped for the resource.
// Each iac provider should append the rules to be skipped for a resource,
// while extracting resource from the iac files
Expand Down
109 changes: 74 additions & 35 deletions pkg/iac-providers/terraform/commons/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
)

type jsonObj map[string]interface{}
type lineObj map[string]interface{}

type converter struct {
bytes []byte
Expand All @@ -42,106 +43,144 @@ func (c *converter) rangeSource(r hcl.Range) string {
return string(c.bytes[r.Start.Byte:r.End.Byte])
}

func (c *converter) convertBody(body *hclsyntax.Body) (jsonObj, error) {
var err error
out := make(jsonObj)
func (c *converter) convertBody(body *hclsyntax.Body) (jsonObj, lineObj, error) {

var (
err error
cfg = make(jsonObj) // resource config
lcfg = make(lineObj) // resource line config
)

// convert attributes
for key, value := range body.Attributes {
out[key], err = c.convertExpression(value.Expr)
cfg[key], lcfg[key], err = c.convertExpression(value.Expr)
if err != nil {
return nil, err
return nil, nil, err
}
}

// convert blocks (nested objects, lists)
for _, block := range body.Blocks {
blockOut := make(jsonObj)
err = c.convertBlock(block, blockOut)

var (
bcfg = make(jsonObj) // block resource config
blcfg = make(lineObj) // block resource line config
)

err = c.convertBlock(block, bcfg, blcfg)
if err != nil {
return nil, err
return nil, nil, err
}
blockConfig := blockOut[block.Type].(jsonObj)
if _, present := out[block.Type]; !present {
out[block.Type] = []jsonObj{blockConfig}

blockConfig := bcfg[block.Type].(jsonObj)
if _, present := cfg[block.Type]; !present {
cfg[block.Type] = []jsonObj{blockConfig}
if lineCfg, ok := blcfg[block.Type].(lineObj); ok {
lcfg[block.Type] = []lineObj{lineCfg}
}

} else {
list := out[block.Type].([]jsonObj)
list := cfg[block.Type].([]jsonObj)
list = append(list, blockConfig)
out[block.Type] = list
cfg[block.Type] = list

if lineList, ok := lcfg[block.Type].([]lineObj); ok {
if lineCfg, ok := blcfg[block.Type].(lineObj); ok {
lineList = append(lineList, lineCfg)
}
lcfg[block.Type] = lineList
}
}
}

return out, nil
return cfg, lcfg, nil
}

func (c *converter) convertBlock(block *hclsyntax.Block, out jsonObj) error {
func (c *converter) convertBlock(block *hclsyntax.Block, cfg jsonObj, lcfg lineObj) error {
var key string = block.Type

value, err := c.convertBody(block.Body)
value, blcfg, err := c.convertBody(block.Body)
if err != nil {
return err
}

for _, label := range block.Labels {
if inner, exists := out[key]; exists {
if inner, exists := cfg[key]; exists {
var ok bool
out, ok = inner.(jsonObj)
cfg, ok = inner.(jsonObj)
if !ok {
// TODO: better diagnostics
return fmt.Errorf("unable to convert Block to JSON: %v.%v", block.Type, strings.Join(block.Labels, "."))
}
} else {
obj := make(jsonObj)
out[key] = obj
out = obj
cfg[key] = obj
cfg = obj
}
key = label
}

if current, exists := out[key]; exists {
// resource config for blocks
if current, exists := cfg[key]; exists {
if list, ok := current.([]interface{}); ok {
cfg[key] = append(list, value)
} else {
cfg[key] = []interface{}{current, value}
}
} else {
cfg[key] = value
}

// resource line config for blocks
if current, exists := lcfg[key]; exists {
if list, ok := current.([]interface{}); ok {
out[key] = append(list, value)
lcfg[key] = append(list, blcfg)
} else {
out[key] = []interface{}{current, value}
lcfg[key] = []interface{}{current, blcfg}
}
} else {
out[key] = value
lcfg[key] = blcfg
}

return nil
}

func (c *converter) convertExpression(expr hclsyntax.Expression) (interface{}, error) {
func (c *converter) convertExpression(expr hclsyntax.Expression) (ret interface{}, line int, err error) {
// assume it is hcl syntax (because, um, it is)
line = expr.StartRange().Start.Line
switch value := expr.(type) {
case *hclsyntax.LiteralValueExpr:
return ctyjson.SimpleJSONValue{Value: value.Val}, nil
return ctyjson.SimpleJSONValue{Value: value.Val}, line, nil
case *hclsyntax.TemplateExpr:
return c.convertTemplate(value)
ret, err = c.convertTemplate(value)
return
case *hclsyntax.TemplateWrapExpr:
return c.convertExpression(value.Wrapped)
case *hclsyntax.TupleConsExpr:
var list []interface{}
for _, ex := range value.Exprs {
elem, err := c.convertExpression(ex)
elem, line, err := c.convertExpression(ex)
if err != nil {
return nil, err
return nil, line, err
}
list = append(list, elem)
}
return list, nil
return list, line, nil
case *hclsyntax.ObjectConsExpr:
m := make(jsonObj)
for _, item := range value.Items {
key, err := c.convertKey(item.KeyExpr)
if err != nil {
return nil, err
return nil, line, err
}
m[key], err = c.convertExpression(item.ValueExpr)
m[key], line, err = c.convertExpression(item.ValueExpr)
if err != nil {
return nil, err
return nil, line, err
}
}
return m, nil
return m, line, nil
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved
default:
return c.wrapExpr(expr), nil
return c.wrapExpr(expr), line, nil
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/iac-providers/terraform/commons/local-references.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (r *RefResolver) ResolveLocalRef(localRef, callerRef string) interface{} {

// extract values from attribute expressions as golang interface{}
c := converter{bytes: fileBytes}
val, err := c.convertExpression(localAttr.Expr.(hclsyntax.Expression))
val, _, err := c.convertExpression(localAttr.Expr.(hclsyntax.Expression))
if err != nil {
zap.S().Errorf("failed to convert expression '%v', ref: '%v'", localAttr.Expr, localRef)
return localRef
Expand Down
3 changes: 2 additions & 1 deletion pkg/iac-providers/terraform/commons/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func CreateResourceConfig(managedResource *hclConfigs.Resource) (resourceConfig
return resourceConfig, fmt.Errorf("failed type assertion for hcl.Body in *hclConfigs.Resource. error: expected hcl.Body type is *hclsyntax.Body, but got %T", managedResource.Config)
}

goOut, err := c.convertBody(hclBody)
goOut, lineOut, err := c.convertBody(hclBody)
if err != nil {
zap.S().Errorf("failed to convert hcl.Body to go struct; resource '%s', file: '%s'. error: '%v'",
managedResource.Name, managedResource.DeclRange.Filename, err)
Expand All @@ -63,6 +63,7 @@ func CreateResourceConfig(managedResource *hclConfigs.Resource) (resourceConfig
Source: managedResource.DeclRange.Filename,
Line: managedResource.DeclRange.Start.Line,
Config: goOut,
LineConfig: lineOut,
SkipRules: utils.GetSkipRules(c.rangeSource(hclBody.Range())),
MaxSeverity: maxSeverity,
MinSeverity: minSeverity,
Expand Down
Loading