229 lines
5.6 KiB
Go
229 lines
5.6 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"reflect"
|
|
"slices"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
|
"github.com/iancoleman/strcase"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Field struct {
|
|
Name string `yaml:"name"`
|
|
Modifiers []*string `yaml:"modifiers"`
|
|
}
|
|
|
|
type Fields struct {
|
|
Fields []*Field `yaml:"fields"`
|
|
}
|
|
|
|
var validModifiers = []string{
|
|
"UseStateForUnknown",
|
|
"RequiresReplace",
|
|
}
|
|
|
|
func ReadModifiersConfig(content []byte) (*Fields, error) {
|
|
var fields Fields
|
|
err := yaml.Unmarshal(content, &fields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &fields, nil
|
|
}
|
|
|
|
func AddPlanModifiersToResourceSchema(fields *Fields, s *schema.Schema) error {
|
|
err := validateFields(fields)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resAttr, err := handleAttributes("", s.Attributes, fields)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.Attributes = resAttr
|
|
|
|
return nil
|
|
}
|
|
|
|
func handleAttributes(prefix string, attributes map[string]schema.Attribute, fields *Fields) (map[string]schema.Attribute, error) {
|
|
fieldMap := fieldListToMap(fields)
|
|
for attrName, attrValue := range attributes {
|
|
attrNameSnake := strcase.ToSnake(attrName)
|
|
if prefix != "" {
|
|
attrNameSnake = prefix + "." + attrNameSnake
|
|
}
|
|
switch reflect.TypeOf(attrValue).String() {
|
|
case "schema.BoolAttribute":
|
|
if field, ok := fieldMap[attrNameSnake]; ok {
|
|
res, err := handleBoolPlanModifiers(attrValue, field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attributes[attrName] = res
|
|
}
|
|
case "schema.Int64Attribute":
|
|
if field, ok := fieldMap[attrNameSnake]; ok {
|
|
res, err := handleInt64PlanModifiers(attrValue, field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attributes[attrName] = res
|
|
}
|
|
case "schema.StringAttribute":
|
|
if field, ok := fieldMap[attrNameSnake]; ok {
|
|
res, err := handleStringPlanModifiers(attrValue, field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attributes[attrName] = res
|
|
}
|
|
case "schema.ListAttribute":
|
|
if field, ok := fieldMap[attrNameSnake]; ok {
|
|
res, err := handleListPlanModifiers(attrValue, field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attributes[attrName] = res
|
|
}
|
|
case "schema.SingleNestedAttribute":
|
|
nested, ok := attrValue.(schema.SingleNestedAttribute)
|
|
if !ok {
|
|
if _, ok := attrValue.(interface {
|
|
GetAttributes() map[string]schema.Attribute
|
|
}); ok {
|
|
return nil, fmt.Errorf("unsupported type for single nested attribute")
|
|
}
|
|
}
|
|
|
|
res, err := handleAttributes(attrName, nested.Attributes, fields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nested.Attributes = res
|
|
attributes[attrName] = nested
|
|
default:
|
|
slog.Warn("type currently not supported", "type", reflect.TypeOf(attrValue).String())
|
|
}
|
|
}
|
|
return attributes, nil
|
|
}
|
|
|
|
func handleBoolPlanModifiers(
|
|
attr schema.Attribute,
|
|
fields []*string,
|
|
) (schema.Attribute, error) {
|
|
a, ok := attr.(schema.BoolAttribute)
|
|
if !ok {
|
|
return nil, fmt.Errorf("field is not a string attribute")
|
|
}
|
|
|
|
for _, v := range fields {
|
|
switch *v {
|
|
case "RequiresReplace":
|
|
a.PlanModifiers = append(a.PlanModifiers, boolplanmodifier.RequiresReplace())
|
|
case "UseStateForUnknown":
|
|
a.PlanModifiers = append(a.PlanModifiers, boolplanmodifier.UseStateForUnknown())
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func handleStringPlanModifiers(
|
|
attr schema.Attribute,
|
|
fields []*string,
|
|
) (schema.Attribute, error) {
|
|
a, ok := attr.(schema.StringAttribute)
|
|
if !ok {
|
|
return nil, fmt.Errorf("field is not a string attribute")
|
|
}
|
|
|
|
for _, v := range fields {
|
|
switch *v {
|
|
case "RequiresReplace":
|
|
a.PlanModifiers = append(a.PlanModifiers, stringplanmodifier.RequiresReplace())
|
|
case "UseStateForUnknown":
|
|
a.PlanModifiers = append(a.PlanModifiers, stringplanmodifier.UseStateForUnknown())
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func handleInt64PlanModifiers(
|
|
attr schema.Attribute,
|
|
fields []*string,
|
|
) (schema.Attribute, error) {
|
|
a, ok := attr.(schema.Int64Attribute)
|
|
if !ok {
|
|
return nil, fmt.Errorf("field is not a string attribute")
|
|
}
|
|
|
|
for _, v := range fields {
|
|
switch *v {
|
|
case "RequiresReplace":
|
|
a.PlanModifiers = append(a.PlanModifiers, int64planmodifier.RequiresReplace())
|
|
case "UseStateForUnknown":
|
|
a.PlanModifiers = append(a.PlanModifiers, int64planmodifier.UseStateForUnknown())
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func handleListPlanModifiers(
|
|
attr schema.Attribute,
|
|
fields []*string,
|
|
) (schema.Attribute, error) {
|
|
a, ok := attr.(schema.ListAttribute)
|
|
if !ok {
|
|
return nil, fmt.Errorf("field is not a string attribute")
|
|
}
|
|
|
|
for _, v := range fields {
|
|
switch *v {
|
|
case "RequiresReplace":
|
|
a.PlanModifiers = append(a.PlanModifiers, listplanmodifier.RequiresReplace())
|
|
case "UseStateForUnknown":
|
|
a.PlanModifiers = append(a.PlanModifiers, listplanmodifier.UseStateForUnknown())
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func validateFields(fields *Fields) error {
|
|
if fields == nil {
|
|
return nil
|
|
}
|
|
for _, field := range fields.Fields {
|
|
for _, modifier := range field.Modifiers {
|
|
if *modifier == "" {
|
|
return fmt.Errorf("modifier %s is required", *modifier)
|
|
}
|
|
if !slices.Contains(validModifiers, *modifier) {
|
|
return fmt.Errorf("modifier %s is invalid", *modifier)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fieldListToMap(fields *Fields) map[string][]*string {
|
|
res := make(map[string][]*string)
|
|
|
|
if fields != nil {
|
|
for _, field := range fields.Fields {
|
|
res[field.Name] = field.Modifiers
|
|
}
|
|
} else {
|
|
slog.Warn("no fields available")
|
|
}
|
|
|
|
return res
|
|
}
|