func findAttribute(body *hclwrite.Body, address string) (*hclwrite.Attribute, *hclwrite.Body, error) {
	if len(address) == 0 {
		return nil, nil, errors.New("failed to parse address. address is empty")
	}
	a := strings.Split(address, ".")
	if len(a) == 1 {
		attr := body.GetAttribute(a[0])
		return attr, body, nil
	}
	attrName := a[len(a)-1]
	blockAddr := strings.Join(a[:len(a)-1], ".")
	blocks, err := findLongestMatchingBlocks(body, blockAddr)
	if err != nil {
		return nil, nil, err
	}
	if len(blocks) == 0 {
		return nil, nil, nil
	}
	for _, b := range blocks {
		attr := b.Body().GetAttribute(attrName)
		if attr != nil {
			return attr, b.Body(), nil
		}
	}
	return nil, nil, nil
}
findAttribute returns first matching attribute at a given address.
If the address does not cantain any dots, find attribute in the body.
If the address contains dots, the last element is an attribute name,
and the rest is the address of the block.
The block is fetched by findLongestMatchingBlocks.
If the attribute is found, the body containing it is also returned for updating.
package editor
import (
	"errors"
	"strings"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/hashicorp/hcl/v2/hclwrite"
)
type AttributeGetSink struct {
	address string
}
var _ Sink = (*AttributeGetSink)(nil)
func NewAttributeGetSink(address string) Sink {
	return &AttributeGetSink{
		address: address,
	}
}
func (s *AttributeGetSink) Sink(inFile *hclwrite.File) ([]byte, error) {
	attr, _, err := findAttribute(inFile.Body(), s.address)
	if err != nil {
		return nil, err
	}
	if attr == nil {
		return []byte{}, nil
	}
	out, err := getAttributeValueAsString(attr)
	if err != nil {
		return []byte{}, err
	}
	return []byte(out + "\n"), nil
}