in resource/context.go [261:443]
func (c *Context) AddHcl(input string, skipWhenDuplicate bool) (*types.Reference, error) {
logrus.Debugf("adding hcl:\n%v, skipWhenDuplicate: %v", input, skipWhenDuplicate)
inputFile, diags := hclwrite.ParseConfig([]byte(input), "", hcl.InitialPos)
if diags.HasErrors() {
logrus.Warnf("failed to parse input:\n%v", input)
return nil, diags
}
labelsMap := make(map[string]map[string]*hclwrite.Block)
for _, block := range c.File.Body().Blocks() {
if block.Type() != "data" && block.Type() != "resource" {
continue
}
labels := block.Labels()
if len(labels) != 2 {
return nil, fmt.Errorf("label is invalid: %v, input:\n%v", labels, input)
}
key := fmt.Sprintf("%s.%s", block.Type(), labels[0])
if labelsMap[key] == nil {
labelsMap[key] = make(map[string]*hclwrite.Block)
}
labelsMap[key][labels[1]] = block
}
// resource/data blocks with same resource name and labels will have conflicts,
// merge them if their resource types are the same,
// otherwise, rename the labels
for _, block := range inputFile.Body().Blocks() {
if block.Type() != "data" && block.Type() != "resource" {
continue
}
labels := block.Labels()
if len(labels) != 2 {
return nil, fmt.Errorf("label is invalid: %v, input:\n%v", labels, input)
}
key := fmt.Sprintf("%s.%s", block.Type(), labels[0])
// no conflict
conflictBlock := labelsMap[key][labels[1]]
if conflictBlock == nil {
continue
}
logrus.Debugf("found conflict: %s %v", conflictBlock.Type(), conflictBlock.Labels())
// if the resource types are the same, there is no conflict
if utils.TypeValue(block) == utils.TypeValue(conflictBlock) && skipWhenDuplicate {
continue
}
// conflict, rename the labels
newLabel := labels[1]
for i := 1; i < 100; i++ {
newLabel = fmt.Sprintf("%s_%d", labels[1], i)
if labelsMap[key][newLabel] == nil {
break
}
}
logrus.Debugf("renaming labels: %v -> %v", labels[1], newLabel)
block.SetLabels([]string{labels[0], newLabel})
input = string(inputFile.BuildTokens(nil).Bytes())
// TODO: improve the following renaming labels logic
oldAddressPrefix := fmt.Sprintf("%s.", strings.Join(labels, "."))
if block.Type() == "data" {
oldAddressPrefix = "data." + oldAddressPrefix
}
newAddressPrefix := fmt.Sprintf("%s.", strings.Join(block.Labels(), "."))
if block.Type() == "data" {
newAddressPrefix = "data." + newAddressPrefix
}
input = strings.ReplaceAll(input, oldAddressPrefix, newAddressPrefix)
}
inputFile, diags = hclwrite.ParseConfig([]byte(input), "", hcl.InitialPos)
if diags.HasErrors() {
logrus.Warnf("failed to parse input:\n%v", input)
return nil, diags
}
// update the location and name fields
for _, block := range inputFile.Body().Blocks() {
if block.Type() != "data" && block.Type() != "resource" {
continue
}
locationAttr := block.Body().GetAttribute("location")
if locationAttr != nil {
defaultLocation := utils.AttributeValue(c.locationVarBlock.Body().GetAttribute("default"))
currentLocation := utils.AttributeValue(locationAttr)
if currentLocation != defaultLocation && !strings.Contains(currentLocation, "var.") {
c.locationVarBlock.Body().SetAttributeValue("default", cty.StringVal(currentLocation))
block.Body().SetAttributeTraversal("location", hcl.Traversal{hcl.TraverseRoot{Name: "var"}, hcl.TraverseAttr{Name: "location"}})
}
}
//TODO: replace location value in the body payload
nameAttr := block.Body().GetAttribute("name")
if nameAttr != nil {
currentName := utils.AttributeValue(nameAttr)
if isRandomName(currentName) {
block.Body().SetAttributeTraversal("name", hcl.Traversal{hcl.TraverseRoot{Name: "var"}, hcl.TraverseAttr{Name: "resource_name"}})
}
}
}
varMap := make(map[string]bool)
providerMap := make(map[string]bool)
for _, block := range c.File.Body().Blocks() {
switch block.Type() {
case "provider":
providerMap[strings.Join(block.Labels(), ".")] = true
case "variable":
varMap[strings.Join(block.Labels(), ".")] = true
}
}
var lastBlock *hclwrite.Block
for _, block := range inputFile.Body().Blocks() {
switch block.Type() {
case "terraform":
newProvidersBlock := block.Body().FirstMatchingBlock("required_providers", []string{})
if newProvidersBlock == nil {
continue
}
oldProvidersBlock := c.terraformBlock.Body().FirstMatchingBlock("required_providers", []string{})
if oldProvidersBlock != nil {
for attrName, attr := range newProvidersBlock.Body().Attributes() {
if oldProvidersBlock.Body().GetAttribute(attrName) == nil {
oldProvidersBlock.Body().SetAttributeRaw(attrName, attr.Expr().BuildTokens(nil))
}
}
} else {
logrus.Errorf("required_providers block not found in the input.")
}
continue
case "variable":
label := strings.Join(block.Labels(), ".")
if varMap[label] {
continue
}
c.File.Body().AppendBlock(block)
case "provider":
label := strings.Join(block.Labels(), ".")
if providerMap[label] {
continue
}
c.File.Body().AppendBlock(block)
case "output":
continue
case "locals":
c.File.Body().AppendBlock(block)
c.File.Body().AppendNewline()
case "data", "resource":
labels := block.Labels()
if len(labels) != 2 {
return nil, fmt.Errorf("label is invalid: %v, input:\n%v", labels, input)
}
key := fmt.Sprintf("%s.%s", block.Type(), labels[0])
conflictBlock := labelsMap[key][labels[1]]
if conflictBlock != nil {
lastBlock = conflictBlock
continue
}
c.File.Body().AppendBlock(block)
c.File.Body().AppendNewline()
lastBlock = block
default:
c.File.Body().AppendBlock(block)
c.File.Body().AppendNewline()
}
}
if lastBlock != nil {
labels := lastBlock.Labels()
if len(labels) != 2 {
return nil, fmt.Errorf("label is invalid: %v, input:\n%v", labels, input)
}
return &types.Reference{
Label: labels[1],
Kind: lastBlock.Type(),
Name: labels[0],
Property: "id",
}, nil
}
return nil, fmt.Errorf("no resource or data block found in the input, input:\n%v", input)
}