diff --git a/apstra/data_source_property_set.go b/apstra/data_source_property_set.go index adc88707..1bbffabd 100644 --- a/apstra/data_source_property_set.go +++ b/apstra/data_source_property_set.go @@ -51,13 +51,13 @@ func (o *dataSourcePropertySet) Read(ctx context.Context, req datasource.ReadReq var ace apstra.ApstraClientErr switch { - case !config.Label.IsNull(): - api, err = o.client.GetPropertySetByLabel(ctx, config.Label.ValueString()) + case !config.Name.IsNull(): + api, err = o.client.GetPropertySetByLabel(ctx, config.Name.ValueString()) if err != nil && errors.As(err, &ace) && ace.Type() == apstra.ErrNotfound { resp.Diagnostics.AddAttributeError( path.Root("name"), "PropertySet not found", - fmt.Sprintf("PropertySet with label %q not found", config.Label.ValueString())) + fmt.Sprintf("PropertySet with label %q not found", config.Name.ValueString())) return } case !config.Id.IsNull(): diff --git a/apstra/design/property_set.go b/apstra/design/property_set.go index 91e6ed27..e7694268 100644 --- a/apstra/design/property_set.go +++ b/apstra/design/property_set.go @@ -13,13 +13,15 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" apstravalidator "terraform-provider-apstra/apstra/apstra_validator" + "terraform-provider-apstra/apstra/utils" ) type PropertySet struct { Id types.String `tfsdk:"id"` - Label types.String `tfsdk:"name"` - Values types.String `tfsdk:"data"` + Name types.String `tfsdk:"name"` + Data types.String `tfsdk:"data"` Blueprints types.Set `tfsdk:"blueprints"` + Keys types.Set `tfsdk:"keys"` } func (o PropertySet) DataSourceAttributes() map[string]dataSourceSchema.Attribute { @@ -42,6 +44,11 @@ func (o PropertySet) DataSourceAttributes() map[string]dataSourceSchema.Attribut Computed: true, Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, + "keys": dataSourceSchema.SetAttribute{ + MarkdownDescription: "Set of keys defined in the Property Set.", + Computed: true, + ElementType: types.StringType, + }, "data": dataSourceSchema.StringAttribute{ MarkdownDescription: "A map of values in the Property Set in JSON format", Computed: true, @@ -68,6 +75,11 @@ func (o PropertySet) ResourceAttributes() map[string]resourceSchema.Attribute { Required: true, Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, + "keys": resourceSchema.SetAttribute{ + MarkdownDescription: "Set of keys defined in the Property Set.", + Computed: true, + ElementType: types.StringType, + }, "data": resourceSchema.StringAttribute{ MarkdownDescription: "A map of values in the Property Set in JSON format", Required: true, @@ -82,16 +94,22 @@ func (o PropertySet) ResourceAttributes() map[string]resourceSchema.Attribute { } func (o *PropertySet) LoadApiData(ctx context.Context, in *apstra.PropertySetData, diags *diag.Diagnostics) { - o.Label = types.StringValue(in.Label) + o.Name = types.StringValue(in.Label) var d diag.Diagnostics o.Blueprints, d = types.SetValueFrom(ctx, types.StringType, in.Blueprints) diags.Append(d...) - o.Values = types.StringValue(string(in.Values)) + o.Data = types.StringValue(string(in.Values)) + k, err := utils.GetKeysFromJSON(o.Data) + if err != nil { + diags.AddError("failed to load keys", err.Error()) + return + } + o.Keys = types.SetValueMust(types.StringType, k) } func (o *PropertySet) Request(_ context.Context, _ *diag.Diagnostics) *apstra.PropertySetData { return &apstra.PropertySetData{ - Label: o.Label.ValueString(), - Values: []byte(o.Values.ValueString()), + Label: o.Name.ValueString(), + Values: []byte(o.Data.ValueString()), } } diff --git a/apstra/resource_property_set.go b/apstra/resource_property_set.go index 0948abf8..18ad8b43 100644 --- a/apstra/resource_property_set.go +++ b/apstra/resource_property_set.go @@ -55,9 +55,14 @@ func (o *resourcePropertySet) Create(ctx context.Context, req resource.CreateReq resp.Diagnostics.AddError("error creating new PropertySet", err.Error()) return } - // Save the tag ID plan.Id = types.StringValue(psid.String()) plan.Blueprints = types.SetNull(types.StringType) + k, err := utils.GetKeysFromJSON(plan.Data) + if err != nil { + resp.Diagnostics.AddError("failed to load keys", err.Error()) + return + } + plan.Keys = types.SetValueMust(types.StringType, k) // set state resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -95,8 +100,8 @@ func (o *resourcePropertySet) Read(ctx context.Context, req resource.ReadRequest if resp.Diagnostics.HasError() { return } - if utils.JSONEqual(newstate.Values, state.Values, &resp.Diagnostics) { - newstate.Values = state.Values + if utils.JSONEqual(newstate.Data, state.Data, &resp.Diagnostics) { + newstate.Data = state.Data } if resp.Diagnostics.HasError() { return @@ -141,13 +146,15 @@ func (o *resourcePropertySet) Update(ctx context.Context, req resource.UpdateReq fmt.Sprintf("PropertySet with ID %q not found. This should not happen", plan.Id.ValueString())) return } - var d design.PropertySet - d.LoadApiData(ctx, api.Data, &resp.Diagnostics) + // save the old data + d := plan.Data + plan.LoadApiData(ctx, api.Data, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } - - plan.Blueprints = d.Blueprints + if utils.JSONEqual(plan.Data, d, &resp.Diagnostics) { + plan.Data = d + } resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } diff --git a/apstra/utils/compare_jsons.go b/apstra/utils/compare_jsons.go index 7dcbbd2f..84d2520f 100644 --- a/apstra/utils/compare_jsons.go +++ b/apstra/utils/compare_jsons.go @@ -2,6 +2,7 @@ package utils import ( "encoding/json" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "reflect" @@ -40,3 +41,23 @@ func IsJSON(str types.String) bool { err := json.Unmarshal([]byte(str.ValueString()), &m) return err == nil } + +// GetKeysFromJSON returns a list of keys from a Json string +func GetKeysFromJSON(str types.String) ([]attr.Value, error) { + m := make(map[string]interface{}) + err := json.Unmarshal([]byte(str.ValueString()), &m) + if err != nil { + return nil, err + } + return getKeysFromMap(m), nil +} + +func getKeysFromMap(m map[string]interface{}) []attr.Value { + keys := make([]attr.Value, len(m)) + i := 0 + for k := range m { + keys[i] = types.StringValue(k) + i++ + } + return keys +} diff --git a/docs/data-sources/property_set.md b/docs/data-sources/property_set.md index 524f2395..ddf09d59 100644 --- a/docs/data-sources/property_set.md +++ b/docs/data-sources/property_set.md @@ -33,9 +33,9 @@ output "apstra_property_set_report" { name = v.name data = jsondecode(v.data) blueprints = v.blueprints + keys = v.keys } } } - ################################################################################ # The output object above will produce something like the following: # apstra_property_set_report = { @@ -44,7 +44,11 @@ output "apstra_property_set_report" { # "data" = { # "nameserver1" = "10.155.191.252" # "nameserver2" = "172.21.200.60" -# } +# }, +# "keys" = toset([ +# "nameserver1", +# "nameserver2", +# ]) # "name" = "nameservers" # } # "7d68daeb-b8f5-4512-9417-9e5812d87783" = { @@ -57,6 +61,10 @@ output "apstra_property_set_report" { # "snmp_collector_01" = "10.6.1.87/32" # "snmp_collector_02" = "10.6.1.88/32" # } +# "keys" = toset([ +# "snmp_collector_01", +# "snmp_collector_02", +# ]) # "name" = "MUST_SNMP_D42" # } # } @@ -75,3 +83,4 @@ output "apstra_property_set_report" { - `blueprints` (Set of String) Set of blueprints that this Property Set might be associated with. - `data` (String) A map of values in the Property Set in JSON format +- `keys` (Set of String) Set of keys defined in the Property Set. diff --git a/docs/data-sources/property_sets.md b/docs/data-sources/property_sets.md index 7cd9cf17..aeccd25e 100644 --- a/docs/data-sources/property_sets.md +++ b/docs/data-sources/property_sets.md @@ -40,7 +40,11 @@ output "apstra_property_set_report" { # "data" = { # "nameserver1" = "10.155.191.252" # "nameserver2" = "172.21.200.60" -# } +# }, +# "keys" = toset([ +# "nameserver1", +# "nameserver2", +# ]) # "name" = "nameservers" # } # "7d68daeb-b8f5-4512-9417-9e5812d87783" = { @@ -53,6 +57,10 @@ output "apstra_property_set_report" { # "snmp_collector_01" = "10.6.1.87/32" # "snmp_collector_02" = "10.6.1.88/32" # } +# "keys" = toset([ +# "snmp_collector_01", +# "snmp_collector_02", +# ]) # "name" = "MUST_SNMP_D42" # } # } diff --git a/docs/resources/property_set.md b/docs/resources/property_set.md index 40bc4e94..2280edc1 100644 --- a/docs/resources/property_set.md +++ b/docs/resources/property_set.md @@ -46,3 +46,4 @@ resource "apstra_property_set" "r" { - `blueprints` (Set of String) Set of blueprints that this Property Set might be associated with. - `id` (String) Populate this field to look up a Property Set by ID. Required when `name` is omitted. +- `keys` (Set of String) Set of keys defined in the Property Set. diff --git a/examples/data-sources/apstra_property_set/example.tf b/examples/data-sources/apstra_property_set/example.tf index fe79ec12..cda422b9 100644 --- a/examples/data-sources/apstra_property_set/example.tf +++ b/examples/data-sources/apstra_property_set/example.tf @@ -16,9 +16,9 @@ output "apstra_property_set_report" { name = v.name data = jsondecode(v.data) blueprints = v.blueprints + keys = v.keys } } } - ################################################################################ # The output object above will produce something like the following: # apstra_property_set_report = { @@ -27,7 +27,11 @@ output "apstra_property_set_report" { # "data" = { # "nameserver1" = "10.155.191.252" # "nameserver2" = "172.21.200.60" -# } +# }, +# "keys" = toset([ +# "nameserver1", +# "nameserver2", +# ]) # "name" = "nameservers" # } # "7d68daeb-b8f5-4512-9417-9e5812d87783" = { @@ -40,7 +44,11 @@ output "apstra_property_set_report" { # "snmp_collector_01" = "10.6.1.87/32" # "snmp_collector_02" = "10.6.1.88/32" # } +# "keys" = toset([ +# "snmp_collector_01", +# "snmp_collector_02", +# ]) # "name" = "MUST_SNMP_D42" # } # } -################################################################################ +################################################################################ \ No newline at end of file diff --git a/examples/data-sources/apstra_property_sets/example.tf b/examples/data-sources/apstra_property_sets/example.tf index 62d7a3c9..a1eb1947 100644 --- a/examples/data-sources/apstra_property_sets/example.tf +++ b/examples/data-sources/apstra_property_sets/example.tf @@ -26,7 +26,11 @@ output "apstra_property_set_report" { # "data" = { # "nameserver1" = "10.155.191.252" # "nameserver2" = "172.21.200.60" -# } +# }, +# "keys" = toset([ +# "nameserver1", +# "nameserver2", +# ]) # "name" = "nameservers" # } # "7d68daeb-b8f5-4512-9417-9e5812d87783" = { @@ -39,6 +43,10 @@ output "apstra_property_set_report" { # "snmp_collector_01" = "10.6.1.87/32" # "snmp_collector_02" = "10.6.1.88/32" # } +# "keys" = toset([ +# "snmp_collector_01", +# "snmp_collector_02", +# ]) # "name" = "MUST_SNMP_D42" # } # }