Skip to content

Commit

Permalink
[ignore] Add a new template and Modify generator to generate custom s…
Browse files Browse the repository at this point in the history
…tring types for attributes that need sementic equality integration for their valid values.
  • Loading branch information
gmicol committed Jul 19, 2024
1 parent cf35393 commit 1139496
Show file tree
Hide file tree
Showing 16 changed files with 1,637 additions and 48 deletions.
29 changes: 25 additions & 4 deletions gen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ var templateFuncs = template.FuncMap{
"getChildAttributesFromBlocks": GetChildAttributesFromBlocks,
"getNewChildAttributes": GetNewChildAttributes,
"containsRequired": ContainsRequired,
"validValuesToMap": ValidValuesToMap,
}

func ContainsRequired(properties map[string]Property) bool {
Expand Down Expand Up @@ -408,6 +409,8 @@ func renderTemplate(templateName, outputFileName, outputPath string, outputData
err = tmpl.Execute(&buffer, outputData.(Model).TestVars)
} else if strings.Contains(templateName, "annotation_unsupported.go.tmpl") {
err = tmpl.Execute(&buffer, outputData.([]string))
} else if strings.Contains(templateName, "custom_type.go.tmpl") {
err = tmpl.Execute(&buffer, outputData.(Property))
} else {
err = tmpl.Execute(&buffer, outputData.(Model))
}
Expand Down Expand Up @@ -586,6 +589,7 @@ func cleanDirectories() {
cleanDirectory(resourcesDocsPath, []string{})
cleanDirectory(datasourcesDocsPath, []string{})
cleanDirectory(testVarsPath, []string{})
cleanDirectory("./internal/custom_types", []string{})

// The *ExamplesPath directories are removed and recreated to ensure all previously rendered files are removed
// The provider example file is not removed because it contains static provider configuration
Expand Down Expand Up @@ -750,6 +754,11 @@ func main() {
panic(err)
}
model.TestVars = testVarsMap
for propertyName, property := range model.Properties {
if len(property.ValidValues) > 0 && len(property.Validators) > 0 {
renderTemplate("custom_type.go.tmpl", fmt.Sprintf("custom_type_%s_%s_%s.go", providerName, model.ResourceName, propertyName), "./internal/custom_types", property)
}
}
renderTemplate("resource.go.tmpl", fmt.Sprintf("resource_%s_%s.go", providerName, model.ResourceName), providerPath, model)
renderTemplate("datasource.go.tmpl", fmt.Sprintf("data_source_%s_%s.go", providerName, model.ResourceName), providerPath, model)

Expand Down Expand Up @@ -908,7 +917,8 @@ type Property struct {
DefaultValue string
Versions string
NamedPropertyClass string
ValidValues []string
ValidValues []interface{}
ValidValuesNames []string
IdentifiedBy []interface{}
Validators []interface{}
IdentifyProperties []Property
Expand Down Expand Up @@ -1358,9 +1368,10 @@ func (m *Model) SetClassProperties(classDetails interface{}) {
if propertyValue.(map[string]interface{})["validValues"] != nil {
removedValidValuesList := GetValidValuesToRemove(m.PkgName, propertyName, m.Definitions)
for _, details := range propertyValue.(map[string]interface{})["validValues"].([]interface{}) {
validValue := details.(map[string]interface{})["localName"].(string)
if validValue != "defaultValue" && !isInSlice(removedValidValuesList, validValue) {
property.ValidValues = append(property.ValidValues, validValue)
validValueName := details.(map[string]interface{})["localName"].(string)
if validValueName != "defaultValue" && !isInSlice(removedValidValuesList, validValueName) {
property.ValidValuesNames = append(property.ValidValuesNames, validValueName)
property.ValidValues = append(property.ValidValues, details)
}
}
if len(property.ValidValues) > 0 {
Expand Down Expand Up @@ -2245,3 +2256,13 @@ func IsInRequiredTestValues(classPkgName, propertyName string, definitions Defin
}
return false
}

func ValidValuesToMap(validValues []interface{}) map[string]string {
validValuesMap := make(map[string]string)
if len(validValues) > 0 {
for _, details := range validValues {
validValuesMap[details.(map[string]interface{})["value"].(string)] = details.(map[string]interface{})["localName"].(string)
}
}
return validValuesMap
}
152 changes: 152 additions & 0 deletions gen/templates/custom_type.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package customtypes

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// {{.ResourceClassName}}{{.PropertyName}} custom string type.

var _ basetypes.StringTypable = {{.ResourceClassName}}{{.PropertyName}}StringType{}

type {{.ResourceClassName}}{{.PropertyName}}StringType struct {
basetypes.StringType
}

func (t {{.ResourceClassName}}{{.PropertyName}}StringType) Equal(o attr.Type) bool {
other, ok := o.({{.ResourceClassName}}{{.PropertyName}}StringType)

if !ok {
return false
}

return t.StringType.Equal(other.StringType)
}

func (t {{.ResourceClassName}}{{.PropertyName}}StringType) String() string {
return "{{.ResourceClassName}}{{.PropertyName}}StringType"
}

func (t {{.ResourceClassName}}{{.PropertyName}}StringType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
value := {{.ResourceClassName}}{{.PropertyName}}StringValue{
StringValue: in,
}

return value, nil
}

func (t {{.ResourceClassName}}{{.PropertyName}}StringType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.StringType.ValueFromTerraform(ctx, in)

if err != nil {
return nil, err
}

stringValue, ok := attrValue.(basetypes.StringValue)

if !ok {
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
}

stringValuable, diags := t.ValueFromString(ctx, stringValue)

if diags.HasError() {
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags)
}

return stringValuable, nil
}

func (t {{.ResourceClassName}}{{.PropertyName}}StringType) ValueType(ctx context.Context) attr.Value {
return {{.ResourceClassName}}{{.PropertyName}}StringValue{}
}

// {{.ResourceClassName}}{{.PropertyName}} custom string value.

var _ basetypes.StringValuableWithSemanticEquals = {{.ResourceClassName}}{{.PropertyName}}StringValue{}

type {{.ResourceClassName}}{{.PropertyName}}StringValue struct {
basetypes.StringValue
}

func (v {{.ResourceClassName}}{{.PropertyName}}StringValue) Equal(o attr.Value) bool {
other, ok := o.({{.ResourceClassName}}{{.PropertyName}}StringValue)

if !ok {
return false
}

return v.StringValue.Equal(other.StringValue)
}

func (v {{.ResourceClassName}}{{.PropertyName}}StringValue) Type(ctx context.Context) attr.Type {
return {{.ResourceClassName}}{{.PropertyName}}StringType{}
}

func (v {{.ResourceClassName}}{{.PropertyName}}StringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
var diags diag.Diagnostics

newValue, ok := newValuable.({{.ResourceClassName}}{{.PropertyName}}StringValue)

if !ok {
diags.AddError(
"Semantic Equality Check Error",
"An unexpected value type was received while performing semantic equality checks. "+
"Please report this to the provider developers.\n\n"+
"Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+
"Got Value Type: "+fmt.Sprintf("%T", newValuable),
)

return false, diags
}

priorMappedValue := {{.ResourceClassName}}{{.PropertyName}}ValueMap(v.StringValue)

newMappedValue := {{.ResourceClassName}}{{.PropertyName}}ValueMap(newValue.StringValue)

return priorMappedValue.Equal(newMappedValue), diags
}

func {{.ResourceClassName}}{{.PropertyName}}ValueMap(value basetypes.StringValue) basetypes.StringValue {
{{- $ValidValuesMap := validValuesToMap .ValidValues}}
matchMap := map[string]string{
{{- range $key, $value := $ValidValuesMap}}
"{{$key}}": "{{$value}}",
{{- end}}
}

if val, ok := matchMap[value.ValueString()]; ok {
return basetypes.NewStringValue(val)
}

return value
}

func New{{.ResourceClassName}}{{.PropertyName}}StringNull() {{.ResourceClassName}}{{.PropertyName}}StringValue {
return {{.ResourceClassName}}{{.PropertyName}}StringValue{
StringValue: basetypes.NewStringNull(),
}
}

func New{{.ResourceClassName}}{{.PropertyName}}StringUnknown() {{.ResourceClassName}}{{.PropertyName}}StringValue {
return {{.ResourceClassName}}{{.PropertyName}}StringValue{
StringValue: basetypes.NewStringUnknown(),
}
}

func New{{.ResourceClassName}}{{.PropertyName}}StringValue(value string) {{.ResourceClassName}}{{.PropertyName}}StringValue {
return {{.ResourceClassName}}{{.PropertyName}}StringValue{
StringValue: basetypes.NewStringValue(value),
}
}

func New{{.ResourceClassName}}{{.PropertyName}}StringPointerValue(value *string) {{.ResourceClassName}}{{.PropertyName}}StringValue {
return {{.ResourceClassName}}{{.PropertyName}}StringValue{
StringValue: basetypes.NewStringPointerValue(value),
}
}
4 changes: 2 additions & 2 deletions gen/templates/datasource.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ Data source for {{.ResourceNameAsDescription}}
{{- range .Properties}}
{{- if .IsNaming}}
* `{{- overwriteProperty .PkgName .SnakeCaseName $.Definitions}}` ({{- .PropertyName}}) - (string) {{.Comment}}{{if ne .NamedPropertyClass ""}}{{$RName := getResourceName .NamedPropertyClass $.Definitions}} This attribute can be referenced from a [resource](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/{{$RName}}) with `aci_{{$RName}}.example.name` or from a [datasource](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/data-sources/{{$RName}}) with `data.aci_{{$RName}}.example.name`.{{end}}{{if and (ne $.Versions .Versions) (ne .Versions "")}} This attribute is supported in ACI versions: {{ .Versions}}{{- end}}
{{- if .ValidValues }}{{$i := 1}}{{$length := len .ValidValues}}
- Valid Values:{{ range .ValidValues}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- if .ValidValuesNames }}{{$i := 1}}{{$length := len .ValidValuesNames}}
- Valid Values:{{ range .ValidValuesNames}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- end}}
{{- end}}
{{- end}}
Expand Down
28 changes: 14 additions & 14 deletions gen/templates/resource.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -831,11 +831,11 @@ func (r *{{.ResourceClassName}}Resource) Schema(ctx context.Context, req resourc
PlanModifiers: []planmodifier.Set{
setplanmodifier.UseStateForUnknown(),
},
{{- if .ValidValues}}
{{- if .ValidValuesNames}}
Validators: []validator.Set{
setvalidator.SizeAtMost({{ len .ValidValues }}),
setvalidator.SizeAtMost({{ len .ValidValuesNames }}),
setvalidator.ValueStringsAre(
stringvalidator.OneOf({{- validatorString .ValidValues}}),
stringvalidator.OneOf({{- validatorString .ValidValuesNames}}),
),
},
{{- end}}
Expand Down Expand Up @@ -863,10 +863,10 @@ func (r *{{.ResourceClassName}}Resource) Schema(ctx context.Context, req resourc
{{- if eq .Name "Annotation"}}
Default: stringdefault.StaticString(globalAnnotation),
{{- end}}
{{- if or .ValidValues (ne $LegacyAttributeName "")}}
{{- if or .ValidValuesNames (ne $LegacyAttributeName "")}}
Validators: []validator.String{
{{- if .ValidValues}}
stringvalidator.OneOf({{- validatorString .ValidValues}}),
{{- if .ValidValuesNames}}
stringvalidator.OneOf({{- validatorString .ValidValuesNames}}),
{{- end}}
{{- if and (ne $LegacyAttributeName "") (or .IsNaming .IsRequired)}}
stringvalidator.AtLeastOneOf(path.Expressions{
Expand Down Expand Up @@ -903,11 +903,11 @@ func (r *{{.ResourceClassName}}Resource) Schema(ctx context.Context, req resourc
PlanModifiers: []planmodifier.Set{
setplanmodifier.UseStateForUnknown(),
},
{{- if .ValidValues}}
{{- if .ValidValuesNames}}
Validators: []validator.Set{
setvalidator.SizeAtMost({{ len .ValidValues }}),
setvalidator.SizeAtMost({{ len .ValidValuesNames }}),
setvalidator.ValueStringsAre(
stringvalidator.OneOf({{- validatorString .ValidValues}}),
stringvalidator.OneOf({{- validatorString .ValidValuesNames}}),
),
},
{{- end}}
Expand All @@ -924,9 +924,9 @@ func (r *{{.ResourceClassName}}Resource) Schema(ctx context.Context, req resourc
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
{{- if .ValidValues}}
{{- if .ValidValuesNames}}
Validators: []validator.String{
stringvalidator.OneOf({{- validatorString .ValidValues}}),
stringvalidator.OneOf({{- validatorString .ValidValuesNames}}),
},
{{- end}}
MarkdownDescription: `{{.Comment}}`,
Expand Down Expand Up @@ -1229,7 +1229,7 @@ func getAndSet{{.ResourceClassName}}Attributes(ctx context.Context, diags *diag.
data.{{.Name}} = {{.PropertyName}}Set
}
{{- else}}
{{- if containsNoneAttributeValue .ValidValues}}
{{- if containsNoneAttributeValue .ValidValuesNames}}
if attributeName == "{{.PropertyName}}" && attributeValue.(string) == "" {
data.{{.Name}} = basetypes.NewStringValue("none")
} else if attributeName == "{{.PropertyName}}" {
Expand Down Expand Up @@ -1264,7 +1264,7 @@ func getAndSet{{.ResourceClassName}}Attributes(ctx context.Context, diags *diag.
for childAttributeName, childAttributeValue := range childAttributes {
{{- range .Properties}}
{{- if eq .ValueType "bitmask"}}
{{- if containsNoneAttributeValue .ValidValues}}
{{- if containsNoneAttributeValue .ValidValuesNames}}
if childAttributeName == "{{.PropertyName}}" && childAttributeValue.(string) == "" {
{{.PropertyName}}Set, _ := types.SetValueFrom(ctx, {{.ResourceClassName}}{{$.ResourceClassName}}.{{.Name}}.ElementType(ctx), []string{"none"})
{{.ResourceClassName}}{{$.ResourceClassName}}.{{.Name}} = {{.PropertyName}}Set
Expand All @@ -1281,7 +1281,7 @@ func getAndSet{{.ResourceClassName}}Attributes(ctx context.Context, diags *diag.
}
{{- end}}
{{- else}}
{{- if containsNoneAttributeValue .ValidValues}}
{{- if containsNoneAttributeValue .ValidValuesNames}}
if childAttributeName == "{{.PropertyName}}" && childAttributeValue.(string) == "" {
{{.ResourceClassName}}{{$.ResourceClassName}}.{{.Name}} = basetypes.NewStringValue("none")
} else if childAttributeName == "{{.PropertyName}}" {
Expand Down
16 changes: 8 additions & 8 deletions gen/templates/resource.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ All examples for the {{.ResourceNameAsDescription}} resource can be found in the
{{- range .Properties}}
{{- if or .IsNaming .IsRequired}}
* `{{- overwriteProperty .PkgName .SnakeCaseName $.Definitions}}` ({{- .PropertyName}}) - (string) {{.Comment}}{{if ne .NamedPropertyClass ""}}{{$RName := getResourceName .NamedPropertyClass $.Definitions}} This attribute can be referenced from a [resource](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/resources/{{$RName}}) with `aci_{{$RName}}.example.name` or from a [datasource](https://registry.terraform.io/providers/CiscoDevNet/aci/latest/docs/data-sources/{{$RName}}) with `data.aci_{{$RName}}.example.name`.{{end}}{{if and (ne $.Versions .Versions) (ne .Versions "")}} This attribute is supported in ACI versions: {{ .Versions}}{{- end}}
{{- if .ValidValues }}{{$i := 1}}{{$length := len .ValidValues}}
- Valid Values:{{ range .ValidValues}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- if .ValidValuesNames }}{{$i := 1}}{{$length := len .ValidValuesNames}}
- Valid Values:{{ range .ValidValuesNames}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- end}}
{{- end}}
{{- end}}
Expand All @@ -103,16 +103,16 @@ All examples for the {{.ResourceNameAsDescription}} resource can be found in the
{{- if .DefaultValue }}
- Default: `{{ .DefaultValue }}`.
{{- end}}
{{- if .ValidValues }}{{$i := 1}}{{$length := len .ValidValues}}
- Valid Values:{{ range .ValidValues}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- if .ValidValuesNames }}{{$i := 1}}{{$length := len .ValidValuesNames}}
- Valid Values:{{ range .ValidValuesNames}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- end}}
{{- else}}
* `{{- overwriteProperty .PkgName .SnakeCaseName $.Definitions}}` ({{- .PropertyName}}) - (string) {{.Comment}}{{if and (ne $.Versions .Versions) (ne .Versions "")}} This attribute is supported in ACI versions: {{ .Versions}}{{- end}}
{{- if .DefaultValue }}
- Default: `{{ .DefaultValue }}`
{{- end}}
{{- if .ValidValues }}{{$i := 1}}{{$length := len .ValidValues}}
- Valid Values:{{ range .ValidValues}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- if .ValidValuesNames }}{{$i := 1}}{{$length := len .ValidValuesNames}}
- Valid Values:{{ range .ValidValuesNames}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- end}}
{{- end}}
{{- end}}
Expand Down Expand Up @@ -154,8 +154,8 @@ All examples for the {{.ResourceNameAsDescription}} resource can be found in the
{{- if .DefaultValue }}
- Default: `{{ .DefaultValue }}`
{{- end}}
{{- if .ValidValues }}{{$i := 1}}{{$length := len .ValidValues}}
- Valid Values:{{ range .ValidValues}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- if .ValidValuesNames }}{{$i := 1}}{{$length := len .ValidValuesNames}}
- Valid Values:{{ range .ValidValuesNames}} `{{ . }}`{{ if ne $length $i}}{{$i = add $i 1}}, {{- else}}.{{- end}}{{- end}}
{{- end}}
{{- end}}
{{- end}}
Expand Down
Loading

0 comments on commit 1139496

Please sign in to comment.