Files
trivy/pkg/iac/terraform/context/context.go
2025-07-29 07:13:54 +00:00

147 lines
2.7 KiB
Go

package context
import (
"maps"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
type Context struct {
ctx *hcl.EvalContext
parent *Context
}
func NewContext(ctx *hcl.EvalContext, parent *Context) *Context {
if ctx.Variables == nil {
ctx.Variables = make(map[string]cty.Value)
}
return &Context{
ctx: ctx,
parent: parent,
}
}
func (c *Context) NewChild() *Context {
return NewContext(c.ctx.NewChild(), c)
}
func (c *Context) Parent() *Context {
return c.parent
}
func (c *Context) Inner() *hcl.EvalContext {
return c.ctx
}
func (c *Context) Root() *Context {
root := c
for root.Parent() != nil {
root = root.Parent()
}
return root
}
func (c *Context) Get(parts ...string) cty.Value {
if len(parts) == 0 {
return cty.NilVal
}
curr := c.ctx.Variables[parts[0]]
if len(parts) == 1 {
return curr
}
for i, part := range parts[1:] {
if !curr.Type().HasAttribute(part) {
return cty.NilVal
}
attr := curr.GetAttr(part)
if i == len(parts)-2 { // iteration from the first element
return attr
}
if !attr.IsKnown() || !attr.Type().IsObjectType() {
return cty.NilVal
}
curr = attr
}
return cty.NilVal
}
func (c *Context) GetByDot(path string) cty.Value {
return c.Get(strings.Split(path, ".")...)
}
func (c *Context) SetByDot(val cty.Value, path string) {
c.Set(val, strings.Split(path, ".")...)
}
func (c *Context) Set(val cty.Value, parts ...string) {
if len(parts) == 0 {
return
}
v := mergeVars(c.ctx.Variables[parts[0]], parts[1:], val)
c.ctx.Variables[parts[0]] = v
}
func (c *Context) Replace(val cty.Value, path string) {
parts := strings.Split(path, ".")
if len(parts) == 0 {
return
}
delete(c.ctx.Variables, parts[0])
c.Set(val, parts...)
}
func mergeVars(src cty.Value, parts []string, value cty.Value) cty.Value {
if len(parts) == 0 {
if isNotEmptyObject(src) && isNotEmptyObject(value) {
return mergeObjects(src, value)
}
return value
}
data := make(map[string]cty.Value)
if isNotEmptyObject(src) {
data = src.AsValueMap()
if attr, ok := data[parts[0]]; ok {
src = attr
} else {
src = cty.EmptyObjectVal
}
}
data[parts[0]] = mergeVars(src, parts[1:], value)
return cty.ObjectVal(data)
}
func mergeObjects(a, b cty.Value) cty.Value {
output := make(map[string]cty.Value)
maps.Copy(output, a.AsValueMap())
b.ForEachElement(func(key, val cty.Value) (stop bool) {
k := key.AsString()
old := output[k]
if old.IsKnown() && isNotEmptyObject(old) && isNotEmptyObject(val) {
output[k] = mergeObjects(old, val)
} else {
output[k] = val
}
return false
})
return cty.ObjectVal(output)
}
func isNotEmptyObject(val cty.Value) bool {
return !val.IsNull() && val.IsKnown() && val.Type().IsObjectType() && val.LengthInt() > 0
}