From 52d09bfbf58ccb87384eb264c7891c901d4be968 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 10:07:16 -0400 Subject: [PATCH 01/15] fix data source attributes --- internal/provider/common.go | 230 +++++++++++++++++++ internal/provider/data_source_ip4_network.go | 167 +++++++------- 2 files changed, 310 insertions(+), 87 deletions(-) create mode 100644 internal/provider/common.go diff --git a/internal/provider/common.go b/internal/provider/common.go new file mode 100644 index 0000000..796b370 --- /dev/null +++ b/internal/provider/common.go @@ -0,0 +1,230 @@ +package provider + +import ( + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/umich-vci/gobam" +) + +// IP4NetworkModel describes the data model the built-in properties for an IP4Network object. +type IP4NetworkModel struct { + // These are exposed via the entity properties field for objects of type IP4Network + CIDR types.String + Template types.Int64 + Gateway types.String + DefaultDomains types.Set + DefaultView types.Int64 + DNSRestrictions types.Set + AllowDuplicateHost types.Bool + PingBeforeAssign types.Bool + InheritAllowDuplicateHost types.Bool + InheritPingBeforeAssign types.Bool + InheritDNSRestrictions types.Bool + InheritDefaultDomains types.Bool + InheritDefaultView types.Bool + LocationCode types.String + LocationInherited types.Bool + SharedNetwork types.String + + // these are user defined fields that are not built-in + UserDefinedFields types.Map +} + +func flattenIP4NetworkProperties(e *gobam.APIEntity) (*IP4NetworkModel, diag.Diagnostics) { + var d diag.Diagnostics + + if e == nil { + d.AddError("invalid input to flattenIP4Network", "entity passed was nil") + return nil, d + } + if e.Type == nil { + d.AddError("invalid input to flattenIP4Network", "type of entity passed was nil") + return nil, d + } else if *e.Type != "IP4Network" { + d.AddError("invalid input to flattenIP4Network", fmt.Sprintf("type of entity passed was %s", *e.Type)) + return nil, d + } + + i := &IP4NetworkModel{} + udfMap := make(map[string]attr.Value) + + var defaultDomainsSet basetypes.SetValue + var dnsRestrictionsSet basetypes.SetValue + defaultDomainsFound := false + dnsRestrictionsFound := false + + if e.Properties != nil { + props := strings.Split(*e.Properties, "|") + for x := range props { + if len(props[x]) > 0 { + prop := strings.Split(props[x], "=")[0] + val := strings.Split(props[x], "=")[1] + + switch prop { + case "name": + // we ignore the name because it is already a top level parameter + case "CIDR": + i.CIDR = types.StringValue(val) + case "template": + t, err := strconv.ParseInt(val, 10, 64) + if err != nil { + d.AddError("error parsing template to int64", err.Error()) + break + } + i.Template = types.Int64Value(t) + case "gateway": + i.Gateway = types.StringValue(val) + case "defaultDomains": + defaultDomainsFound = true + var ddDiag diag.Diagnostics + defaultDomains := strings.Split(val, ",") + defaultDomainsList := []attr.Value{} + for x := range defaultDomains { + dID, err := strconv.ParseInt(defaultDomains[x], 10, 64) + if err != nil { + d.AddError("error parsing defaultDomains to int64", err.Error()) + break + } + defaultDomainsList = append(defaultDomainsList, types.Int64Value(dID)) + } + + defaultDomainsSet, ddDiag = basetypes.NewSetValue(types.Int64Type, defaultDomainsList) + if ddDiag.HasError() { + d.Append(ddDiag...) + break + } + case "defaultView": + dv, err := strconv.ParseInt(val, 10, 64) + if err != nil { + d.AddError("error parsing defaultView to int64", err.Error()) + break + } + i.DefaultView = types.Int64Value(dv) + case "dnsRestrictions": + dnsRestrictionsFound = true + var drDiag diag.Diagnostics + dnsRestrictions := strings.Split(val, ",") + didList := []attr.Value{} + for x := range dnsRestrictions { + dID, err := strconv.ParseInt(dnsRestrictions[x], 10, 64) + if err != nil { + d.AddError("error parsing dnsRestrictions to int64", err.Error()) + break + } + didList = append(didList, types.Int64Value(dID)) + } + dnsRestrictionsSet, drDiag = basetypes.NewSetValue(types.Int64Type, didList) + if drDiag.HasError() { + d.Append(drDiag...) + } + case "allowDuplicateHost": + i.AllowDuplicateHost = types.BoolPointerValue(enableDisableToBool(val)) + case "pingBeforeAssign": + i.PingBeforeAssign = types.BoolPointerValue(enableDisableToBool(val)) + case "inheritAllowDuplicateHost": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing inheritAllowDuplicateHost to bool", err.Error()) + break + } + i.InheritAllowDuplicateHost = types.BoolValue(b) + case "inheritPingBeforeAssign": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing inheritPingBeforeAssign to bool", err.Error()) + break + } + i.InheritPingBeforeAssign = types.BoolValue(b) + case "inheritDNSRestrictions": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing inheritDNSRestrictions to bool", err.Error()) + break + } + i.InheritDNSRestrictions = types.BoolValue(b) + case "inheritDefaultDomains": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing inheritDefaultDomains to bool", err.Error()) + break + } + i.InheritDefaultDomains = types.BoolValue(b) + case "inheritDefaultView": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing inheritDefaultView to bool", err.Error()) + break + } + i.InheritDefaultView = types.BoolValue(b) + case "locationCode": + i.LocationCode = types.StringValue(val) + case "locationInherited": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing locationInherited to bool", err.Error()) + break + } + i.LocationInherited = types.BoolValue(b) + case "sharedNetwork": + i.SharedNetwork = types.StringValue(val) + default: + udfMap[prop] = types.StringValue(val) + } + } + } + } + + if !dnsRestrictionsFound { + dnsRestrictionsSet = basetypes.NewSetNull(types.Int64Type) + } + i.DNSRestrictions = dnsRestrictionsSet + + if !defaultDomainsFound { + defaultDomainsSet = basetypes.NewSetNull(types.Int64Type) + } + i.DefaultDomains = defaultDomainsSet + + var userDefinedFields basetypes.MapValue + userDefinedFields, udfDiag := basetypes.NewMapValue(types.StringType, udfMap) + if udfDiag.HasError() { + d.Append(udfDiag...) + } + i.UserDefinedFields = userDefinedFields + + return i, d +} + +func enableDisableToBool(s string) *bool { + var val *bool + + switch s { + case "enable": + val = new(bool) + *val = true + case "disable": + val = new(bool) + *val = false + default: + val = nil + } + return val +} + +func boolToEnableDisable(b *bool) string { + var s string + + if b == nil { + s = "" + } else if *b { + s = "enable" + } else { + s = "disable" + } + return s +} diff --git a/internal/provider/data_source_ip4_network.go b/internal/provider/data_source_ip4_network.go index 0224ab7..8a89471 100644 --- a/internal/provider/data_source_ip4_network.go +++ b/internal/provider/data_source_ip4_network.go @@ -24,30 +24,36 @@ type IP4NetworkDataSource struct { // IP4NetworkDataSourceModel describes the data source data model. type IP4NetworkDataSourceModel struct { - ID types.Int64 `tfsdk:"id"` - ContainerID types.Int64 `tfsdk:"container_id"` - Hint types.String `tfsdk:"hint"` - Type types.String `tfsdk:"type"` - AddressesFree types.Int64 `tfsdk:"addresses_free"` - AddressesInUse types.Int64 `tfsdk:"addresses_in_use"` - AllowDuplicateHost types.String `tfsdk:"allow_duplicate_host"` + // These are exposed for a generic entity object in bluecat + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Properties types.String `tfsdk:"properties"` + + // These are exposed via the entity properties field for objects of type IP4Network CIDR types.String `tfsdk:"cidr"` - CustomProperties types.Map `tfsdk:"custom_properties"` + Template types.Int64 `tfsdk:"template"` + Gateway types.String `tfsdk:"gateway"` DefaultDomains types.Set `tfsdk:"default_domains"` DefaultView types.Int64 `tfsdk:"default_view"` DNSRestrictions types.Set `tfsdk:"dns_restrictions"` - Gateway types.String `tfsdk:"gateway"` + AllowDuplicateHost types.Bool `tfsdk:"allow_duplicate_host"` + PingBeforeAssign types.Bool `tfsdk:"ping_before_assign"` InheritAllowDuplicateHost types.Bool `tfsdk:"inherit_allow_duplicate_host"` + InheritPingBeforeAssign types.Bool `tfsdk:"inherit_ping_before_assign"` + InheritDNSRestrictions types.Bool `tfsdk:"inherit_dns_restrictions"` InheritDefaultDomains types.Bool `tfsdk:"inherit_default_domains"` InheritDefaultView types.Bool `tfsdk:"inherit_default_view"` - InheritDNSRestrictions types.Bool `tfsdk:"inherit_dns_restrictions"` - InheritPingBeforeAssign types.Bool `tfsdk:"inherit_ping_before_assign"` LocationCode types.String `tfsdk:"location_code"` LocationInherited types.Bool `tfsdk:"location_inherited"` - Name types.String `tfsdk:"name"` - PingBeforeAssign types.String `tfsdk:"ping_before_assign"` - Properties types.String `tfsdk:"properties"` - Template types.Int64 `tfsdk:"template"` + SharedNetwork types.String `tfsdk:"shared_network"` + + // these are user defined fields that are not built-in + UserDefinedFields types.Map `tfsdk:"user_defined_fields"` + + // these exist only for the data source to find the network + ContainerID types.Int64 `tfsdk:"container_id"` + Hint types.String `tfsdk:"hint"` } func (d *IP4NetworkDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -60,45 +66,44 @@ func (d *IP4NetworkDataSource) Schema(ctx context.Context, req datasource.Schema MarkdownDescription: "Data source to access the attributes of an IPv4 network from a hint based search.", Attributes: map[string]schema.Attribute{ - "id": schema.Int64Attribute{ - MarkdownDescription: "Example identifier", - Computed: true, + "container_id": schema.Int64Attribute{ + MarkdownDescription: "The object ID of a container that contains the specified IPv4 network.", + Required: true, }, "hint": schema.StringAttribute{ MarkdownDescription: "Hint to find the IP4Network", Required: true, }, - "container_id": schema.Int64Attribute{ - MarkdownDescription: "The object ID of a container that contains the specified IPv4 network.", - Required: true, - }, - "type": schema.StringAttribute{ - MarkdownDescription: "The type of the IP4Network", + "id": schema.Int64Attribute{ + MarkdownDescription: "The ID assigned to the IP4Network.", Computed: true, }, - "addresses_free": schema.Int64Attribute{ - MarkdownDescription: "The number of addresses unallocated/free on the network.", + "name": schema.StringAttribute{ + MarkdownDescription: "The name assigned to the IP4Network.", Computed: true, }, - "addresses_in_use": schema.Int64Attribute{ - MarkdownDescription: "The number of addresses allocated/in use on the network.", + "properties": schema.StringAttribute{ + MarkdownDescription: "The properties of the IP4Network (pipe delimited).", Computed: true, }, - "allow_duplicate_host": schema.StringAttribute{ - MarkdownDescription: "Duplicate host names check.", + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the entity.", Computed: true, }, "cidr": schema.StringAttribute{ - MarkdownDescription: "The CIDR address of the IPv4 network.", + MarkdownDescription: "The CIDR address of the IP4Network.", Computed: true, }, - "custom_properties": schema.MapAttribute{ - MarkdownDescription: "A map of all custom properties associated with the IPv4 network.", + "template": schema.Int64Attribute{ + MarkdownDescription: "The ID of the linked template", + Computed: true, + }, + "gateway": schema.StringAttribute{ + MarkdownDescription: "The gateway of the IP4Network.", Computed: true, - ElementType: types.StringType, }, "default_domains": schema.SetAttribute{ - MarkdownDescription: "TODO", + MarkdownDescription: "The object ids of the default DNS domains for the network.", Computed: true, ElementType: types.Int64Type, }, @@ -107,57 +112,54 @@ func (d *IP4NetworkDataSource) Schema(ctx context.Context, req datasource.Schema Computed: true, }, "dns_restrictions": schema.SetAttribute{ - MarkdownDescription: "TODO", + MarkdownDescription: "The object ids of the DNS restrictions for the network.", Computed: true, ElementType: types.Int64Type, }, - "gateway": schema.StringAttribute{ - MarkdownDescription: "The gateway of the IPv4 network.", + "allow_duplicate_host": schema.BoolAttribute{ + MarkdownDescription: "Duplicate host names check.", Computed: true, }, - "inherit_allow_duplicate_host": schema.BoolAttribute{ - MarkdownDescription: "Duplicate host names check is inherited.", + "ping_before_assign": schema.BoolAttribute{ + MarkdownDescription: "The network pings an address before assignment.", Computed: true, }, - "inherit_default_domains": schema.BoolAttribute{ - MarkdownDescription: "Default domains are inherited.", + "inherit_allow_duplicate_host": schema.BoolAttribute{ + MarkdownDescription: "Duplicate host names check is inherited.", Computed: true, }, - "inherit_default_view": schema.BoolAttribute{ - MarkdownDescription: "The default DNS View is inherited.", + "inherit_ping_before_assign": schema.BoolAttribute{ + MarkdownDescription: "The network pings an address before assignment is inherited.", Computed: true, }, "inherit_dns_restrictions": schema.BoolAttribute{ MarkdownDescription: "DNS restrictions are inherited.", Computed: true, }, - "inherit_ping_before_assign": schema.BoolAttribute{ - MarkdownDescription: "The network pings an address before assignment is inherited.", - Computed: true, - }, - "location_code": schema.StringAttribute{ - MarkdownDescription: "TODO", + "inherit_default_domains": schema.BoolAttribute{ + MarkdownDescription: "Default domains are inherited.", Computed: true, }, - "location_inherited": schema.BoolAttribute{ - MarkdownDescription: "TODO", + "inherit_default_view": schema.BoolAttribute{ + MarkdownDescription: "The default DNS View is inherited.", Computed: true, }, - "name": schema.StringAttribute{ - MarkdownDescription: "The name assigned the resource.", + "location_code": schema.StringAttribute{ + MarkdownDescription: "The location code of the network.", Computed: true, }, - "ping_before_assign": schema.StringAttribute{ - MarkdownDescription: "The network pings an address before assignment.", + "location_inherited": schema.BoolAttribute{ + MarkdownDescription: "The location is inherited.", Computed: true, }, - "properties": schema.StringAttribute{ - MarkdownDescription: "The properties of the resource as returned by the API (pipe delimited).", + "shared_network": schema.StringAttribute{ + MarkdownDescription: "The name of the shared network tag associated with the IP4 Network.", Computed: true, }, - "template": schema.Int64Attribute{ - MarkdownDescription: "TODO", + "user_defined_fields": schema.MapAttribute{ + MarkdownDescription: "A map of all user-definied fields associated with the entity.", Computed: true, + ElementType: types.StringType, }, }, } @@ -236,39 +238,30 @@ func (d *IP4NetworkDataSource) Read(ctx context.Context, req datasource.ReadRequ data.Properties = types.StringPointerValue(entity.Properties) data.Type = types.StringPointerValue(entity.Type) - networkProperties, diag := parseIP4NetworkProperties(*entity.Properties) + networkProperties, diag := flattenIP4NetworkProperties(entity) if diag.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) resp.Diagnostics.Append(diag...) return } - data.CIDR = networkProperties.cidr - data.Template = networkProperties.template - - data.Gateway = networkProperties.gateway - data.DefaultDomains = networkProperties.defaultDomains - data.DefaultView = networkProperties.defaultView - data.DNSRestrictions = networkProperties.dnsRestrictions - data.AllowDuplicateHost = networkProperties.allowDuplicateHost - data.PingBeforeAssign = networkProperties.pingBeforeAssign - data.InheritAllowDuplicateHost = networkProperties.inheritAllowDuplicateHost - data.InheritPingBeforeAssign = networkProperties.inheritPingBeforeAssign - data.InheritDNSRestrictions = networkProperties.inheritDNSRestrictions - data.InheritDefaultDomains = networkProperties.inheritDefaultDomains - data.InheritDefaultView = networkProperties.inheritDefaultView - data.LocationCode = networkProperties.locationCode - data.LocationInherited = networkProperties.locationInherited - data.CustomProperties = networkProperties.customProperties - - addressesInUse, addressesFree, err := getIP4NetworkAddressUsage(*entity.Id, networkProperties.cidr.ValueString(), client) - if err != nil { - resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.AddError("Error calculating network usage", err.Error()) - return - } - data.AddressesInUse = types.Int64Value(addressesInUse) - data.AddressesFree = types.Int64Value(addressesFree) + data.CIDR = networkProperties.CIDR + data.Template = networkProperties.Template + data.Gateway = networkProperties.Gateway + data.DefaultDomains = networkProperties.DefaultDomains + data.DefaultView = networkProperties.DefaultView + data.DNSRestrictions = networkProperties.DNSRestrictions + data.AllowDuplicateHost = networkProperties.AllowDuplicateHost + data.PingBeforeAssign = networkProperties.PingBeforeAssign + data.InheritAllowDuplicateHost = networkProperties.InheritAllowDuplicateHost + data.InheritPingBeforeAssign = networkProperties.InheritPingBeforeAssign + data.InheritDNSRestrictions = networkProperties.InheritDNSRestrictions + data.InheritDefaultDomains = networkProperties.InheritDefaultDomains + data.InheritDefaultView = networkProperties.InheritDefaultView + data.LocationCode = networkProperties.LocationCode + data.LocationInherited = networkProperties.LocationInherited + data.SharedNetwork = networkProperties.SharedNetwork + data.UserDefinedFields = networkProperties.UserDefinedFields resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) From ae81d704c11b622cdf2d049d29a2d58bbcd36711 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 12:44:48 -0400 Subject: [PATCH 02/15] update and create working correctly all attributes --- internal/provider/resource_ip4_network.go | 542 ++++++++++++++++++---- 1 file changed, 449 insertions(+), 93 deletions(-) diff --git a/internal/provider/resource_ip4_network.go b/internal/provider/resource_ip4_network.go index 5471b67..e7ded56 100644 --- a/internal/provider/resource_ip4_network.go +++ b/internal/provider/resource_ip4_network.go @@ -3,7 +3,9 @@ package provider import ( "context" "fmt" + "regexp" "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" @@ -14,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -35,27 +38,38 @@ type IP4NetworkResource struct { // IP4NetworkResourceModel describes the resource data model. type IP4NetworkResourceModel struct { - ID types.Int64 `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ParentID types.Int64 `tfsdk:"parent_id"` - Size types.Int64 `tfsdk:"size"` - IsLargerAllowed types.Bool `tfsdk:"is_larger_allowed"` - TraversalMethod types.String `tfsdk:"traversal_method"` - AddressesInUse types.Int64 `tfsdk:"addresses_in_use"` - AddressesFree types.Int64 `tfsdk:"addresses_free"` - AllowDuplicateHost types.String `tfsdk:"allow_duplicate_host"` + // These are exposed for a generic entity object in bluecat + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Properties types.String `tfsdk:"properties"` + + // These are exposed via the entity properties field for objects of type IP4Network CIDR types.String `tfsdk:"cidr"` - CustomProperties types.Map `tfsdk:"custom_properties"` - DefaultView types.Int64 `tfsdk:"default_view"` + Template types.Int64 `tfsdk:"template"` Gateway types.String `tfsdk:"gateway"` + DefaultDomains types.Set `tfsdk:"default_domains"` + DefaultView types.Int64 `tfsdk:"default_view"` + DNSRestrictions types.Set `tfsdk:"dns_restrictions"` + AllowDuplicateHost types.Bool `tfsdk:"allow_duplicate_host"` + PingBeforeAssign types.Bool `tfsdk:"ping_before_assign"` InheritAllowDuplicateHost types.Bool `tfsdk:"inherit_allow_duplicate_host"` + InheritPingBeforeAssign types.Bool `tfsdk:"inherit_ping_before_assign"` + InheritDNSRestrictions types.Bool `tfsdk:"inherit_dns_restrictions"` InheritDefaultDomains types.Bool `tfsdk:"inherit_default_domains"` InheritDefaultView types.Bool `tfsdk:"inherit_default_view"` - InheritDNSRestrictions types.Bool `tfsdk:"inherit_dns_restrictions"` - InheritPingBeforeAssign types.Bool `tfsdk:"inherit_ping_before_assign"` - PingBeforeAssign types.String `tfsdk:"ping_before_assign"` - Properties types.String `tfsdk:"properties"` - Type types.String `tfsdk:"type"` + LocationCode types.String `tfsdk:"location_code"` + LocationInherited types.Bool `tfsdk:"location_inherited"` + SharedNetwork types.String `tfsdk:"shared_network"` + + // these are user defined fields that are not built-in + UserDefinedFields types.Map `tfsdk:"user_defined_fields"` + + // These fields are only used for creation + IsLargerAllowed types.Bool `tfsdk:"is_larger_allowed"` + ParentID types.Int64 `tfsdk:"parent_id"` + Size types.Int64 `tfsdk:"size"` + TraversalMethod types.String `tfsdk:"traversal_method"` } func (r *IP4NetworkResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -68,6 +82,7 @@ func (r *IP4NetworkResource) Schema(ctx context.Context, req resource.SchemaRequ MarkdownDescription: "Resource to create an IPv4 network.", Attributes: map[string]schema.Attribute{ + // These are exposed for Entity objects via the API "id": schema.Int64Attribute{ MarkdownDescription: "IPv4 Network identifier.", Computed: true, @@ -77,7 +92,25 @@ func (r *IP4NetworkResource) Schema(ctx context.Context, req resource.SchemaRequ }, "name": schema.StringAttribute{ MarkdownDescription: "The display name of the IPv4 network.", - Required: true, + Optional: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the resource.", + Computed: true, + }, + "properties": schema.StringAttribute{ + MarkdownDescription: "The properties of the resource as returned by the API (pipe delimited).", + Computed: true, + }, + // These fields are only used for creation and are not exposed via the API entity + "is_larger_allowed": schema.BoolAttribute{ + MarkdownDescription: "(Optional) Is it ok to return a network that is larger than the size specified?", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, }, "parent_id": schema.Int64Attribute{ MarkdownDescription: "The object ID of the parent object that will contain the new IPv4 network. If this argument is changed, then the resource will be recreated.", @@ -93,15 +126,6 @@ func (r *IP4NetworkResource) Schema(ctx context.Context, req resource.SchemaRequ int64planmodifier.RequiresReplace(), }, }, - "is_larger_allowed": schema.BoolAttribute{ - MarkdownDescription: "(Optional) Is it ok to return a network that is larger than the size specified?", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, "traversal_method": schema.StringAttribute{ MarkdownDescription: "The traversal method used to find the range to allocate the network. Must be one of \"NO_TRAVERSAL\", \"DEPTH_FIRST\", or \"BREADTH_FIRST\".", Optional: true, @@ -110,67 +134,111 @@ func (r *IP4NetworkResource) Schema(ctx context.Context, req resource.SchemaRequ Validators: []validator.String{ stringvalidator.OneOf("NO_TRAVERSAL", "DEPTH_FIRST", "BREADTH_FIRST"), }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, - "addresses_in_use": schema.Int64Attribute{ - MarkdownDescription: "The number of addresses allocated/in use on the network.", - Computed: true, - }, - "addresses_free": schema.Int64Attribute{ - MarkdownDescription: "The number of addresses unallocated/free on the network.", + + // These are exposed via the API properties field for objects of type IP4Network + "cidr": schema.StringAttribute{ + MarkdownDescription: "The CIDR address of the IPv4 network.", Computed: true, }, - "allow_duplicate_host": schema.StringAttribute{ - MarkdownDescription: "Duplicate host names check.", + "template": schema.Int64Attribute{ + MarkdownDescription: "The ID of the linked template", Computed: true, }, - "cidr": schema.StringAttribute{ - MarkdownDescription: "The CIDR address of the IPv4 network.", + "gateway": schema.StringAttribute{ + MarkdownDescription: "The gateway of the IPv4 network.", Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`), "Gateway must be a valid IPv4 address"), + }, }, - "custom_properties": schema.MapAttribute{ - MarkdownDescription: "A map of all custom properties associated with the IPv4 network.", + "default_domains": schema.SetAttribute{ + MarkdownDescription: "The object ids of the default DNS domains for the network.", Computed: true, - ElementType: types.StringType, + Optional: true, + ElementType: types.Int64Type, + Default: nil, }, "default_view": schema.Int64Attribute{ MarkdownDescription: "The object id of the default DNS View for the network.", Computed: true, + Optional: true, + Default: nil, + }, + "dns_restrictions": schema.SetAttribute{ + MarkdownDescription: "The object ids of the DNS restrictions for the network.", + Computed: true, + Optional: true, + ElementType: types.Int64Type, + Default: nil, }, - "gateway": schema.StringAttribute{ - MarkdownDescription: "The gateway of the IPv4 network.", + "allow_duplicate_host": schema.BoolAttribute{ + MarkdownDescription: "Duplicate host names check.", + Computed: true, + Optional: true, + Default: nil, + }, + "ping_before_assign": schema.BoolAttribute{ + MarkdownDescription: "The network pings an address before assignment.", Computed: true, + Optional: true, + Default: nil, }, "inherit_allow_duplicate_host": schema.BoolAttribute{ MarkdownDescription: "Duplicate host names check is inherited.", Computed: true, + Optional: true, + Default: booldefault.StaticBool(true), + }, + "inherit_ping_before_assign": schema.BoolAttribute{ + MarkdownDescription: "The network pings an address before assignment is inherited.", + Computed: true, + Optional: true, + Default: booldefault.StaticBool(true), + }, + "inherit_dns_restrictions": schema.BoolAttribute{ + MarkdownDescription: "DNS restrictions are inherited.", + Computed: true, + Optional: true, + Default: booldefault.StaticBool(true), }, "inherit_default_domains": schema.BoolAttribute{ MarkdownDescription: "Default domains are inherited.", Computed: true, + Optional: true, + Default: booldefault.StaticBool(true), }, "inherit_default_view": schema.BoolAttribute{ - MarkdownDescription: "The default DNS Viewis inherited.", - Computed: true, - }, - "inherit_dns_restrictions": schema.BoolAttribute{ - MarkdownDescription: "DNS restrictions are inherited.", + MarkdownDescription: "The default DNS View is inherited.", Computed: true, + Optional: true, + Default: booldefault.StaticBool(true), }, - "inherit_ping_before_assign": schema.BoolAttribute{ - MarkdownDescription: "The network pings an address before assignment is inherited.", + "location_code": schema.StringAttribute{ + MarkdownDescription: "The location code of the network.", Computed: true, + Optional: true, + Default: nil, + Validators: []validator.String{ + // The code is case-sensitive and must be in uppercase letters. The country code and child location code should be alphanumeric strings. + }, }, - "ping_before_assign": schema.StringAttribute{ - MarkdownDescription: "The network pings an address before assignment.", + "location_inherited": schema.BoolAttribute{ + MarkdownDescription: "The location is inherited.", Computed: true, }, - "properties": schema.StringAttribute{ - MarkdownDescription: "The properties of the resource as returned by the API (pipe delimited).", + "shared_network": schema.StringAttribute{ + MarkdownDescription: "The name of the shared network tag associated with the IP4 Network.", Computed: true, }, - "type": schema.StringAttribute{ - MarkdownDescription: "The type of the resource.", + "user_defined_fields": schema.MapAttribute{ + MarkdownDescription: "A map of all user-definied fields associated with the IP4 Network.", Computed: true, + ElementType: types.StringType, }, }, } @@ -236,28 +304,122 @@ func (r *IP4NetworkResource) Create(ctx context.Context, req resource.CreateRequ } data.ID = types.Int64PointerValue(network.Id) - name := data.Name.ValueString() - id := *network.Id + data.Properties = types.StringPointerValue(network.Properties) + data.Type = types.StringPointerValue(network.Type) + + // we have an ID at this point so save the state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + properties = "" - otype := "IP4Network" + + if !data.Gateway.IsUnknown() { + properties = properties + "gateway=" + data.Gateway.ValueString() + "|" + } + + if !data.DefaultDomains.IsUnknown() { + var defaultDomains []string + data.DefaultDomains.ElementsAs(ctx, &defaultDomains, false) + properties = properties + "defaultDomains=" + strings.Join(defaultDomains, ",") + "|" + } + + if !data.DefaultView.IsUnknown() { + properties = properties + "defaultView=" + strconv.FormatInt(data.DefaultView.ValueInt64(), 10) + "|" + } + + if !data.DNSRestrictions.IsUnknown() { + var dnsRestrictions []string + data.DNSRestrictions.ElementsAs(ctx, &dnsRestrictions, false) + properties = properties + "dnsRestrictions=" + strings.Join(dnsRestrictions, ",") + "|" + } + + if !data.AllowDuplicateHost.IsUnknown() { + properties = properties + "allowDuplicateHost=" + boolToEnableDisable(data.AllowDuplicateHost.ValueBoolPointer()) + "|" + } + + if !data.PingBeforeAssign.IsUnknown() { + properties = properties + "pingBeforeAssign=" + boolToEnableDisable(data.PingBeforeAssign.ValueBoolPointer()) + "|" + } + + if !data.InheritAllowDuplicateHost.IsUnknown() { + properties = properties + "inheritAllowDuplicateHost=" + strconv.FormatBool(data.InheritAllowDuplicateHost.ValueBool()) + "|" + } + + if !data.InheritPingBeforeAssign.IsUnknown() { + properties = properties + "inheritPingBeforeAssign=" + strconv.FormatBool(data.InheritPingBeforeAssign.ValueBool()) + "|" + } + + if !data.InheritDNSRestrictions.IsUnknown() { + properties = properties + "inheritDNSRestrictions=" + strconv.FormatBool(data.InheritDNSRestrictions.ValueBool()) + "|" + } + + if !data.InheritDefaultDomains.IsUnknown() { + properties = properties + "inheritDefaultDomains=" + strconv.FormatBool(data.InheritDefaultDomains.ValueBool()) + "|" + } + + if !data.InheritDefaultView.IsUnknown() { + properties = properties + "inheritDefaultView=" + strconv.FormatBool(data.InheritDefaultView.ValueBool()) + "|" + } + + if !data.LocationCode.IsUnknown() { + properties = properties + "locationCode=" + data.LocationCode.ValueString() + "|" + } setName := gobam.APIEntity{ - Id: &id, - Name: &name, + Id: data.ID.ValueInt64Pointer(), + Name: data.Name.ValueStringPointer(), Properties: &properties, - Type: &otype, + Type: data.Type.ValueStringPointer(), } err = client.Update(&setName) if err != nil { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) resp.Diagnostics.AddError( - "Failed to update created IP4 Network with name", + "Failed to update created IP4 Network", + err.Error(), + ) + + return + } + + entity, err := client.GetEntityById(data.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.AddError( + "Failed to get IP4 Network by Id", err.Error(), ) return } + networkProperties, diag := flattenIP4NetworkProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + + data.Name = types.StringPointerValue(entity.Name) + data.Properties = types.StringPointerValue(entity.Properties) + data.Type = types.StringPointerValue(entity.Type) + data.CIDR = networkProperties.CIDR + data.Template = networkProperties.Template + data.Gateway = networkProperties.Gateway + data.DefaultDomains = networkProperties.DefaultDomains + data.DefaultView = networkProperties.DefaultView + data.DNSRestrictions = networkProperties.DNSRestrictions + data.AllowDuplicateHost = networkProperties.AllowDuplicateHost + data.PingBeforeAssign = networkProperties.PingBeforeAssign + data.InheritAllowDuplicateHost = networkProperties.InheritAllowDuplicateHost + data.InheritPingBeforeAssign = networkProperties.InheritPingBeforeAssign + data.InheritDNSRestrictions = networkProperties.InheritDNSRestrictions + data.InheritDefaultDomains = networkProperties.InheritDefaultDomains + data.InheritDefaultView = networkProperties.InheritDefaultView + data.LocationCode = networkProperties.LocationCode + data.LocationInherited = networkProperties.LocationInherited + data.SharedNetwork = networkProperties.SharedNetwork + data.UserDefinedFields = networkProperties.UserDefinedFields + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) // Write logs using the tflog package @@ -314,37 +476,30 @@ func (r *IP4NetworkResource) Read(ctx context.Context, req resource.ReadRequest, data.Properties = types.StringPointerValue(entity.Properties) data.Type = types.StringPointerValue(entity.Type) - networkProperties, diag := parseIP4NetworkProperties(*entity.Properties) + networkProperties, diag := flattenIP4NetworkProperties(entity) if diag.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) resp.Diagnostics.Append(diag...) return } - data.CIDR = networkProperties.cidr - data.AllowDuplicateHost = networkProperties.allowDuplicateHost - data.InheritAllowDuplicateHost = networkProperties.inheritAllowDuplicateHost - data.InheritPingBeforeAssign = networkProperties.inheritPingBeforeAssign - data.PingBeforeAssign = networkProperties.pingBeforeAssign - data.Gateway = networkProperties.gateway - data.InheritDefaultDomains = networkProperties.inheritDefaultDomains - data.DefaultView = networkProperties.defaultView - data.InheritDefaultView = networkProperties.inheritDefaultView - data.InheritDNSRestrictions = networkProperties.inheritDNSRestrictions - data.CustomProperties = networkProperties.customProperties - - addressesInUse, addressesFree, err := getIP4NetworkAddressUsage(*entity.Id, networkProperties.cidr.ValueString(), client) - if err != nil { - resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.AddError( - "Error calculating network usage", - err.Error(), - ) - return - } - - data.AddressesInUse = types.Int64Value(addressesInUse) - data.AddressesFree = types.Int64Value(addressesFree) + data.CIDR = networkProperties.CIDR + data.Template = networkProperties.Template + data.Gateway = networkProperties.Gateway + data.DefaultDomains = networkProperties.DefaultDomains + data.DefaultView = networkProperties.DefaultView + data.DNSRestrictions = networkProperties.DNSRestrictions + data.AllowDuplicateHost = networkProperties.AllowDuplicateHost + data.PingBeforeAssign = networkProperties.PingBeforeAssign + data.InheritAllowDuplicateHost = networkProperties.InheritAllowDuplicateHost + data.InheritPingBeforeAssign = networkProperties.InheritPingBeforeAssign + data.InheritDNSRestrictions = networkProperties.InheritDNSRestrictions + data.InheritDefaultDomains = networkProperties.InheritDefaultDomains + data.InheritDefaultView = networkProperties.InheritDefaultView + data.LocationCode = networkProperties.LocationCode + data.LocationInherited = networkProperties.LocationInherited + data.SharedNetwork = networkProperties.SharedNetwork + data.UserDefinedFields = networkProperties.UserDefinedFields resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) @@ -353,11 +508,13 @@ func (r *IP4NetworkResource) Read(ctx context.Context, req resource.ReadRequest, } func (r *IP4NetworkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data *IP4NetworkResourceModel + var data, state *IP4NetworkResourceModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { return } @@ -369,18 +526,78 @@ func (r *IP4NetworkResource) Update(ctx context.Context, req resource.UpdateRequ return } - id := data.ID.ValueInt64() - name := data.Name.ValueString() properties := "" - otype := "IP4Network" + + if !data.Gateway.IsUnknown() && !data.Gateway.Equal(state.Gateway) { + properties = properties + fmt.Sprintf("gateway=%s|", data.Gateway.ValueString()) + } + + if !data.DefaultDomains.IsUnknown() && !data.DefaultDomains.Equal(state.DefaultDomains) { + var domains []string + data.DefaultDomains.ElementsAs(ctx, &domains, false) + if domains != nil { + properties = properties + fmt.Sprintf("defaultDomains=%s|", strings.Join(domains, ",")) + } + } + + if !data.DefaultView.IsUnknown() && !data.DefaultView.Equal(state.DefaultView) { + + properties = properties + fmt.Sprintf("defaultView=%s|", strconv.FormatInt(data.DefaultView.ValueInt64(), 10)) + + } + + if !data.DNSRestrictions.IsUnknown() && !data.DNSRestrictions.Equal(state.DNSRestrictions) { + var dns []string + data.DNSRestrictions.ElementsAs(ctx, &dns, false) + if dns != nil { + properties = properties + fmt.Sprintf("dnsRestrictions=%s|", dns) + } + + } + + if !data.AllowDuplicateHost.IsUnknown() && !data.AllowDuplicateHost.Equal(state.AllowDuplicateHost) { + properties = properties + fmt.Sprintf("allowDuplicateHost=%s|", boolToEnableDisable(data.AllowDuplicateHost.ValueBoolPointer())) + + } + + if !data.PingBeforeAssign.IsUnknown() && !data.PingBeforeAssign.Equal(state.PingBeforeAssign) { + properties = properties + fmt.Sprintf("pingBeforeAssign=%s|", boolToEnableDisable(data.PingBeforeAssign.ValueBoolPointer())) + } + + if !data.InheritAllowDuplicateHost.Equal(state.InheritAllowDuplicateHost) { + properties = properties + fmt.Sprintf("inheritAllowDuplicateHost=%s|", strconv.FormatBool(data.InheritAllowDuplicateHost.ValueBool())) + } + + if !data.InheritPingBeforeAssign.Equal(state.InheritPingBeforeAssign) { + properties = properties + fmt.Sprintf("inheritPingBeforeAssign=%s|", strconv.FormatBool(data.InheritPingBeforeAssign.ValueBool())) + } + + if !data.InheritDNSRestrictions.Equal(state.InheritDNSRestrictions) { + properties = properties + fmt.Sprintf("inheritDNSRestrictions=%s|", strconv.FormatBool(data.InheritDNSRestrictions.ValueBool())) + } + + if !data.InheritDefaultDomains.Equal(state.InheritDefaultDomains) { + properties = properties + fmt.Sprintf("inheritDefaultDomains=%s|", strconv.FormatBool(data.InheritDefaultDomains.ValueBool())) + + } + + if !data.InheritDefaultView.Equal(state.InheritDefaultView) { + properties = properties + fmt.Sprintf("inheritDefaultView=%s|", strconv.FormatBool(data.InheritDefaultView.ValueBool())) + } + + if !data.LocationCode.IsUnknown() && !data.LocationCode.Equal(state.LocationCode) { + properties = properties + fmt.Sprintf("locationCode=%s|", data.LocationCode.ValueString()) + } update := gobam.APIEntity{ - Id: &id, - Name: &name, + Id: state.ID.ValueInt64Pointer(), + Name: data.Name.ValueStringPointer(), Properties: &properties, - Type: &otype, + Type: state.Type.ValueStringPointer(), } + tflog.Debug(ctx, fmt.Sprintf("Attempting to update IP4Network with properties: %s", properties)) + err := client.Update(&update) if err != nil { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) @@ -391,6 +608,45 @@ func (r *IP4NetworkResource) Update(ctx context.Context, req resource.UpdateRequ return } + entity, err := client.GetEntityById(data.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.AddError( + "Failed to get IP4 Network by Id", + err.Error(), + ) + return + } + + data.Name = types.StringPointerValue(entity.Name) + data.Properties = types.StringPointerValue(entity.Properties) + data.Type = types.StringPointerValue(entity.Type) + + networkProperties, diag := flattenIP4NetworkProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + + data.CIDR = networkProperties.CIDR + data.Template = networkProperties.Template + data.Gateway = networkProperties.Gateway + data.DefaultDomains = networkProperties.DefaultDomains + data.DefaultView = networkProperties.DefaultView + data.DNSRestrictions = networkProperties.DNSRestrictions + data.AllowDuplicateHost = networkProperties.AllowDuplicateHost + data.PingBeforeAssign = networkProperties.PingBeforeAssign + data.InheritAllowDuplicateHost = networkProperties.InheritAllowDuplicateHost + data.InheritPingBeforeAssign = networkProperties.InheritPingBeforeAssign + data.InheritDNSRestrictions = networkProperties.InheritDNSRestrictions + data.InheritDefaultDomains = networkProperties.InheritDefaultDomains + data.InheritDefaultView = networkProperties.InheritDefaultView + data.LocationCode = networkProperties.LocationCode + data.LocationInherited = networkProperties.LocationInherited + data.SharedNetwork = networkProperties.SharedNetwork + data.UserDefinedFields = networkProperties.UserDefinedFields + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) // Save updated data into Terraform state @@ -447,3 +703,103 @@ func (r *IP4NetworkResource) Delete(ctx context.Context, req resource.DeleteRequ func (r *IP4NetworkResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } + +func (r IP4NetworkResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var data IP4NetworkResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // if inherit_allow_duplicate_host is true, allow_duplicate_host must be unset + if data.InheritAllowDuplicateHost.ValueBool() && !data.AllowDuplicateHost.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("allow_duplicate_host"), + "Attribute Conflict", + "allow_duplicate_host cannot be configured if inherit_allow_duplicate_host is true.", + ) + } + + // if inherit_allow_duplicate_host is false, allow_duplicate_host must be set + if !data.InheritAllowDuplicateHost.ValueBool() && data.AllowDuplicateHost.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("allow_duplicate_host"), + "Attribute Conflict", + "allow_duplicate_host must be configured if inherit_allow_duplicate_host is false.", + ) + } + + // if inherit_dns_restrictions is true, dns_restrictions must be unset + if data.InheritDNSRestrictions.ValueBool() && !data.DNSRestrictions.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("dns_restrictions"), + "Attribute Conflict", + "dns_restrictions cannot be configured if inherit_dns_restrictions is true.", + ) + } + + // if inherit_dns_restrictions is false, dns_restrictions must be set + if !data.InheritDNSRestrictions.ValueBool() && data.DNSRestrictions.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("dns_restrictions"), + "Attribute Conflict", + "allow_duplicate_host must be configured if inherit_allow_duplicate_host is false.", + ) + } + + // if inherit_default_domains is true, default_domains must be unset + if data.InheritDefaultDomains.ValueBool() && !data.DefaultDomains.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("default_domains"), + "Attribute Conflict", + "default_domains cannot be configured if inherit_default_domains is true.", + ) + } + + // if inherit_default_domains is false, default_domains must be set + if !data.InheritDefaultDomains.ValueBool() && data.DefaultDomains.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("default_domains"), + "Attribute Conflict", + "default_domains must be configured if inherit_default_domains is false.", + ) + } + + // if inherit_default_view is true, default_view must be unset + if data.InheritDefaultView.ValueBool() && !data.DefaultView.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("default_view"), + "Attribute Conflict", + "default_view cannot be configured if inherit_default_view is true.", + ) + } + + // if inherit_default_view is false, default_view must be set + if !data.InheritDefaultView.ValueBool() && data.DefaultView.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("default_view"), + "Attribute Conflict", + "default_view must be configured if inherit_default_view is false.", + ) + } + + // if inherit_ping_before_assign is true, ping_before_assign must be unset + if data.InheritPingBeforeAssign.ValueBool() && !data.PingBeforeAssign.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("ping_before_assign"), + "Attribute Conflict", + "ping_before_assign cannot be configured if inherit_ping_before_assign is true.", + ) + } + + // if inherit_ping_before_assign is false, ping_before_assign must be set + if !data.InheritPingBeforeAssign.ValueBool() && data.PingBeforeAssign.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("ping_before_assign"), + "Attribute Conflict", + "ping_before_assign must be configured if inherit_ping_before_assign is false.", + ) + } +} From 2f4531a95facdef5c5a38c3a4efb2446df85f4ad Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 12:55:33 -0400 Subject: [PATCH 03/15] fix network deleted outside terraform --- internal/provider/resource_ip4_network.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/provider/resource_ip4_network.go b/internal/provider/resource_ip4_network.go index e7ded56..a01dfc2 100644 --- a/internal/provider/resource_ip4_network.go +++ b/internal/provider/resource_ip4_network.go @@ -460,15 +460,9 @@ func (r *IP4NetworkResource) Read(ctx context.Context, req resource.ReadRequest, } if *entity.Id == 0 { - data.ID = types.Int64Null() + tflog.Trace(ctx, "IP4 Network was deleted outside terraform") resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.AddError( - "Failed to create IP4 Network", - "ID returned was 0", - ) - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) - + resp.State.RemoveResource(ctx) return } From 60ec70073995bed38c9fd1ef9e9af86ab1c482e1 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 12:57:39 -0400 Subject: [PATCH 04/15] fix resource deleted outside terraform --- internal/provider/resource_host_record.go | 2 +- internal/provider/resource_ip4_address.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/provider/resource_host_record.go b/internal/provider/resource_host_record.go index 46d467c..be2df53 100644 --- a/internal/provider/resource_host_record.go +++ b/internal/provider/resource_host_record.go @@ -231,8 +231,8 @@ func (r *HostRecordResource) Read(ctx context.Context, req resource.ReadRequest, } if *entity.Id == 0 { - data.ID = types.Int64Null() resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.State.RemoveResource(ctx) return } diff --git a/internal/provider/resource_ip4_address.go b/internal/provider/resource_ip4_address.go index d175836..f94285a 100644 --- a/internal/provider/resource_ip4_address.go +++ b/internal/provider/resource_ip4_address.go @@ -217,9 +217,8 @@ func (r *IP4AddressResource) Read(ctx context.Context, req resource.ReadRequest, } if *entity.Id == 0 { - data.ID = types.Int64Null() resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + resp.State.RemoveResource(ctx) return } From 375f043d77b1f9859d5d36489f3de0ac1cf27252 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 13:00:24 -0400 Subject: [PATCH 05/15] update dependencies --- go.mod | 35 ++++++++++++----------- go.sum | 89 +++++++++++++++++++++++++--------------------------------- 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/go.mod b/go.mod index 964f2db..7e1315e 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.22 require ( github.com/hashicorp/terraform-plugin-docs v0.18.0 - github.com/hashicorp/terraform-plugin-framework v1.6.0 + github.com/hashicorp/terraform-plugin-framework v1.6.1 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 - github.com/hashicorp/terraform-plugin-go v0.22.0 + github.com/hashicorp/terraform-plugin-go v0.22.1 github.com/hashicorp/terraform-plugin-log v0.9.0 - github.com/hashicorp/terraform-plugin-testing v1.6.0 + github.com/hashicorp/terraform-plugin-testing v1.7.0 github.com/umich-vci/gobam v0.0.0-20230705194030-32758b9f0f3c ) @@ -17,7 +17,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect @@ -27,23 +27,23 @@ require ( github.com/fiorix/wsdl2go v1.4.7 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/cli v1.1.6 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hc-install v0.6.2 // indirect - github.com/hashicorp/hcl/v2 v2.19.1 // indirect + github.com/hashicorp/hc-install v0.6.3 // indirect + github.com/hashicorp/hcl/v2 v2.20.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.20.0 // indirect github.com/hashicorp/terraform-json v0.21.0 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -67,16 +67,17 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yuin/goldmark v1.6.0 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.14.1 // indirect - golang.org/x/crypto v0.17.0 // indirect + github.com/zclconf/go-cty v1.14.3 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/grpc v1.61.1 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/grpc v1.62.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index c80614c..490915d 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= +github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -23,8 +23,6 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -45,8 +43,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -61,8 +59,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -75,8 +73,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= @@ -87,10 +85,10 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.6.2 h1:V1k+Vraqz4olgZ9UzKiAcbman9i9scg9GgSt/U3mw/M= -github.com/hashicorp/hc-install v0.6.2/go.mod h1:2JBpd+NCFKiHiu/yYCGaPyPHhZLxXTpz8oreHa/a3Ps= -github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= -github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hc-install v0.6.3 h1:yE/r1yJvWbtrJ0STwScgEnCanb0U9v7zp0Gbkmcoxqs= +github.com/hashicorp/hc-install v0.6.3/go.mod h1:KamGdbodYzlufbWh4r9NRo8y6GLHWZP2GBtdnms1Ln0= +github.com/hashicorp/hcl/v2 v2.20.0 h1:l++cRs/5jQOiKVvqXZm/P1ZEfVXJmvLS9WSVxkaeTb4= +github.com/hashicorp/hcl/v2 v2.20.0/go.mod h1:WmcD/Ym72MDOOx5F62Ly+leloeu6H7m0pG7VBiU6pQk= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= @@ -99,18 +97,18 @@ github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRy github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= github.com/hashicorp/terraform-plugin-docs v0.18.0 h1:2bINhzXc+yDeAcafurshCrIjtdu1XHn9zZ3ISuEhgpk= github.com/hashicorp/terraform-plugin-docs v0.18.0/go.mod h1:iIUfaJpdUmpi+rI42Kgq+63jAjI8aZVTyxp3Bvk9Hg8= -github.com/hashicorp/terraform-plugin-framework v1.6.0 h1:hMPWoCiNGR+yzoDlXtZ/meGlUOCn8r1OFuPG84MkhWg= -github.com/hashicorp/terraform-plugin-framework v1.6.0/go.mod h1:QRG6J+m5QBJum+lzKi0Ci2CB8a/xflS3T/aWoz8WD4Y= +github.com/hashicorp/terraform-plugin-framework v1.6.1 h1:hw2XrmUu8d8jVL52ekxim2IqDc+2Kpekn21xZANARLU= +github.com/hashicorp/terraform-plugin-framework v1.6.1/go.mod h1:aJI+n/hBPhz1J+77GdgNfk5svW12y7fmtxe/5L5IuwI= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= -github.com/hashicorp/terraform-plugin-go v0.22.0 h1:1OS1Jk5mO0f5hrziWJGXXIxBrMe2j/B8E+DVGw43Xmc= -github.com/hashicorp/terraform-plugin-go v0.22.0/go.mod h1:mPULV91VKss7sik6KFEcEu7HuTogMLLO/EvWCuFkRVE= +github.com/hashicorp/terraform-plugin-go v0.22.1 h1:iTS7WHNVrn7uhe3cojtvWWn83cm2Z6ryIUDTRO0EV7w= +github.com/hashicorp/terraform-plugin-go v0.22.1/go.mod h1:qrjnqRghvQ6KnDbB12XeZ4FluclYwptntoWCr9QaXTI= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 h1:X7vB6vn5tON2b49ILa4W7mFAsndeqJ7bZFOGbVO+0Cc= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0/go.mod h1:ydFcxbdj6klCqYEPkPvdvFKiNGKZLUs+896ODUXCyao= -github.com/hashicorp/terraform-plugin-testing v1.6.0 h1:Wsnfh+7XSVRfwcr2jZYHsnLOnZl7UeaOBvsx6dl/608= -github.com/hashicorp/terraform-plugin-testing v1.6.0/go.mod h1:cJGG0/8j9XhHaJZRC+0sXFI4uzqQZ9Az4vh6C4GJpFE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0/go.mod h1:H+8tjs9TjV2w57QFVSMBQacf8k/E1XwLXGCARgViC6A= +github.com/hashicorp/terraform-plugin-testing v1.7.0 h1:I6aeCyZ30z4NiI3tzyDoO6fS7YxP5xSL1ceOon3gTe8= +github.com/hashicorp/terraform-plugin-testing v1.7.0/go.mod h1:sbAreCleJNOCz+y5vVHV8EJkIWZKi/t4ndKiUjM9vao= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -205,34 +203,30 @@ github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= -github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.14.3 h1:1JXy1XroaGrzZuG6X9dt7HL6s9AwbY+l4UNL8o5B6ho= +github.com/zclconf/go-cty v1.14.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -245,30 +239,23 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -276,14 +263,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 86ea4d42ab11e18fe4fe5b74320b4a1b1562b5b5 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 13:05:13 -0400 Subject: [PATCH 06/15] regenerate docs --- docs/data-sources/ip4_network.md | 31 +++++++++++++++---------------- docs/resources/ip4_network.md | 30 +++++++++++++++++------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/docs/data-sources/ip4_network.md b/docs/data-sources/ip4_network.md index f5501c6..2c48edf 100644 --- a/docs/data-sources/ip4_network.md +++ b/docs/data-sources/ip4_network.md @@ -22,25 +22,24 @@ Data source to access the attributes of an IPv4 network from a hint based search ### Read-Only -- `addresses_free` (Number) The number of addresses unallocated/free on the network. -- `addresses_in_use` (Number) The number of addresses allocated/in use on the network. -- `allow_duplicate_host` (String) Duplicate host names check. -- `cidr` (String) The CIDR address of the IPv4 network. -- `custom_properties` (Map of String) A map of all custom properties associated with the IPv4 network. -- `default_domains` (Set of Number) TODO +- `allow_duplicate_host` (Boolean) Duplicate host names check. +- `cidr` (String) The CIDR address of the IP4Network. +- `default_domains` (Set of Number) The object ids of the default DNS domains for the network. - `default_view` (Number) The object id of the default DNS View for the network. -- `dns_restrictions` (Set of Number) TODO -- `gateway` (String) The gateway of the IPv4 network. -- `id` (Number) Example identifier +- `dns_restrictions` (Set of Number) The object ids of the DNS restrictions for the network. +- `gateway` (String) The gateway of the IP4Network. +- `id` (Number) The ID assigned to the IP4Network. - `inherit_allow_duplicate_host` (Boolean) Duplicate host names check is inherited. - `inherit_default_domains` (Boolean) Default domains are inherited. - `inherit_default_view` (Boolean) The default DNS View is inherited. - `inherit_dns_restrictions` (Boolean) DNS restrictions are inherited. - `inherit_ping_before_assign` (Boolean) The network pings an address before assignment is inherited. -- `location_code` (String) TODO -- `location_inherited` (Boolean) TODO -- `name` (String) The name assigned the resource. -- `ping_before_assign` (String) The network pings an address before assignment. -- `properties` (String) The properties of the resource as returned by the API (pipe delimited). -- `template` (Number) TODO -- `type` (String) The type of the IP4Network +- `location_code` (String) The location code of the network. +- `location_inherited` (Boolean) The location is inherited. +- `name` (String) The name assigned to the IP4Network. +- `ping_before_assign` (Boolean) The network pings an address before assignment. +- `properties` (String) The properties of the IP4Network (pipe delimited). +- `shared_network` (String) The name of the shared network tag associated with the IP4 Network. +- `template` (Number) The ID of the linked template +- `type` (String) The type of the entity. +- `user_defined_fields` (Map of String) A map of all user-definied fields associated with the entity. diff --git a/docs/resources/ip4_network.md b/docs/resources/ip4_network.md index 86f6c69..da0d6aa 100644 --- a/docs/resources/ip4_network.md +++ b/docs/resources/ip4_network.md @@ -29,30 +29,34 @@ output "bluecat_ip4_network_cidr" { ### Required -- `name` (String) The display name of the IPv4 network. - `parent_id` (Number) The object ID of the parent object that will contain the new IPv4 network. If this argument is changed, then the resource will be recreated. - `size` (Number) The size of the IPv4 network expressed as a power of 2. For example, 256 would create a /24. If this argument is changed, then the resource will be recreated. ### Optional +- `allow_duplicate_host` (Boolean) Duplicate host names check. +- `default_domains` (Set of Number) The object ids of the default DNS domains for the network. +- `default_view` (Number) The object id of the default DNS View for the network. +- `dns_restrictions` (Set of Number) The object ids of the DNS restrictions for the network. +- `gateway` (String) The gateway of the IPv4 network. +- `inherit_allow_duplicate_host` (Boolean) Duplicate host names check is inherited. +- `inherit_default_domains` (Boolean) Default domains are inherited. +- `inherit_default_view` (Boolean) The default DNS View is inherited. +- `inherit_dns_restrictions` (Boolean) DNS restrictions are inherited. +- `inherit_ping_before_assign` (Boolean) The network pings an address before assignment is inherited. - `is_larger_allowed` (Boolean) (Optional) Is it ok to return a network that is larger than the size specified? +- `location_code` (String) The location code of the network. +- `name` (String) The display name of the IPv4 network. +- `ping_before_assign` (Boolean) The network pings an address before assignment. - `traversal_method` (String) The traversal method used to find the range to allocate the network. Must be one of "NO_TRAVERSAL", "DEPTH_FIRST", or "BREADTH_FIRST". ### Read-Only -- `addresses_free` (Number) The number of addresses unallocated/free on the network. -- `addresses_in_use` (Number) The number of addresses allocated/in use on the network. -- `allow_duplicate_host` (String) Duplicate host names check. - `cidr` (String) The CIDR address of the IPv4 network. -- `custom_properties` (Map of String) A map of all custom properties associated with the IPv4 network. -- `default_view` (Number) The object id of the default DNS View for the network. -- `gateway` (String) The gateway of the IPv4 network. - `id` (Number) IPv4 Network identifier. -- `inherit_allow_duplicate_host` (Boolean) Duplicate host names check is inherited. -- `inherit_default_domains` (Boolean) Default domains are inherited. -- `inherit_default_view` (Boolean) The default DNS Viewis inherited. -- `inherit_dns_restrictions` (Boolean) DNS restrictions are inherited. -- `inherit_ping_before_assign` (Boolean) The network pings an address before assignment is inherited. -- `ping_before_assign` (String) The network pings an address before assignment. +- `location_inherited` (Boolean) The location is inherited. - `properties` (String) The properties of the resource as returned by the API (pipe delimited). +- `shared_network` (String) The name of the shared network tag associated with the IP4 Network. +- `template` (Number) The ID of the linked template - `type` (String) The type of the resource. +- `user_defined_fields` (Map of String) A map of all user-definied fields associated with the IP4 Network. From 366aaf89faffb4cd2a084f6f8c76fe2b59d3d138 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 15:10:53 -0400 Subject: [PATCH 07/15] flatten function for IP4Address --- internal/provider/common.go | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/internal/provider/common.go b/internal/provider/common.go index 796b370..46317f9 100644 --- a/internal/provider/common.go +++ b/internal/provider/common.go @@ -228,3 +228,94 @@ func boolToEnableDisable(b *bool) string { } return s } + +// IP4AddressModel describes the data model the built-in properties for an IP4Address object. +type IP4AddressModel struct { + // These are exposed via the entity properties field for objects of type IP4Network + Address types.String + State types.String + MACAddress types.String + RouterPortInfo types.String + SwitchPortInfo types.String + VLANInfo types.String + LeaseTime types.String + ExpiryTime types.String + ParameterRequestList types.String + VendorClassIdentifier types.String + LocationCode types.String + LocationInherited types.Bool + + // these are user defined fields that are not built-in + UserDefinedFields types.Map +} + +func flattenIP4AddressProperties(e *gobam.APIEntity) (*IP4AddressModel, diag.Diagnostics) { + var d diag.Diagnostics + + if e == nil { + d.AddError("invalid input to flattenIP4Network", "entity passed was nil") + return nil, d + } + if e.Type == nil { + d.AddError("invalid input to flattenIP4Network", "type of entity passed was nil") + return nil, d + } else if *e.Type != "IP4Address" { + d.AddError("invalid input to flattenIP4Address", fmt.Sprintf("type of entity passed was %s", *e.Type)) + return nil, d + } + + i := &IP4AddressModel{} + udfMap := make(map[string]attr.Value) + + if e.Properties != nil { + props := strings.Split(*e.Properties, "|") + for x := range props { + if len(props[x]) > 0 { + prop := strings.Split(props[x], "=")[0] + val := strings.Split(props[x], "=")[1] + + switch prop { + case "address": + i.Address = types.StringValue(val) + case "state": + i.State = types.StringValue(val) + case "macAddress": + i.MACAddress = types.StringValue(val) + case "routerPortInfo": + i.RouterPortInfo = types.StringValue(val) + case "switchPortInfo": + i.SwitchPortInfo = types.StringValue(val) + case "vlanInfo": + i.VLANInfo = types.StringValue(val) + case "leaseTime": + i.LeaseTime = types.StringValue(val) + case "expiryTime": + i.ExpiryTime = types.StringValue(val) + case "parameterRequestList": + i.ParameterRequestList = types.StringValue(val) + case "vendorClassIdentifier": + i.VendorClassIdentifier = types.StringValue(val) + case "locationCode": + i.LocationCode = types.StringValue(val) + case "locationInherited": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing locationInherited to bool", err.Error()) + break + } + i.LocationInherited = types.BoolValue(b) + default: + udfMap[prop] = types.StringValue(val) + } + } + } + } + + var userDefinedFields basetypes.MapValue + userDefinedFields, udfDiag := basetypes.NewMapValue(types.StringType, udfMap) + if udfDiag.HasError() { + d.Append(udfDiag...) + } + i.UserDefinedFields = userDefinedFields + return i, d +} From 861839ee81116cae34ec922ba096d3d57c96be94 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Mon, 18 Mar 2024 15:11:41 -0400 Subject: [PATCH 08/15] implement full schema for ip4 address --- internal/provider/data_source_ip4_address.go | 98 +++---- internal/provider/resource_ip4_address.go | 281 ++++++++++++++----- 2 files changed, 255 insertions(+), 124 deletions(-) diff --git a/internal/provider/data_source_ip4_address.go b/internal/provider/data_source_ip4_address.go index 2a69085..22fc5b7 100644 --- a/internal/provider/data_source_ip4_address.go +++ b/internal/provider/data_source_ip4_address.go @@ -3,9 +3,7 @@ package provider import ( "context" "fmt" - "strings" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" @@ -25,15 +23,31 @@ type IP4AddressDataSource struct { // IP4AddressDataSourceModel describes the data source data model. type IP4AddressDataSourceModel struct { - ID types.Int64 `tfsdk:"id"` - Address types.String `tfsdk:"address"` - ContainerID types.Int64 `tfsdk:"container_id"` - CustomProperties types.Map `tfsdk:"custom_properties"` - MACAddress types.String `tfsdk:"mac_address"` - Name types.String `tfsdk:"name"` - Properties types.String `tfsdk:"properties"` - State types.String `tfsdk:"state"` - Type types.String `tfsdk:"type"` + // These are exposed for a generic entity object in bluecat + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Properties types.String `tfsdk:"properties"` + + // This is used to help find the IP4Address + ContainerID types.Int64 `tfsdk:"container_id"` + + // These are exposed via the entity properties field for objects of type IP4Address + Address types.String `tfsdk:"address"` + State types.String `tfsdk:"state"` + MACAddress types.String `tfsdk:"mac_address"` + RouterPortInfo types.String `tfsdk:"router_port_info"` + SwitchPortInfo types.String `tfsdk:"switch_port_info"` + VLANInfo types.String `tfsdk:"vlan_info"` + LeaseTime types.String `tfsdk:"lease_time"` + ExpiryTime types.String `tfsdk:"expiry_time"` + ParameterRequestList types.String `tfsdk:"parameter_request_list"` + VendorClassIdentifier types.String `tfsdk:"vendor_class_identifier"` + LocationCode types.String `tfsdk:"location_code"` + LocationInherited types.Bool `tfsdk:"location_inherited"` + + // these are user defined fields that are not built-in + UserDefinedFields types.Map `tfsdk:"user_defined_fields"` } func (d *IP4AddressDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -138,56 +152,28 @@ func (d *IP4AddressDataSource) Read(ctx context.Context, req datasource.ReadRequ data.Properties = types.StringPointerValue(ip4Address.Properties) data.Type = types.StringPointerValue(ip4Address.Type) - addressProperties, err := parseIP4AddressProperties(*ip4Address.Properties) - if err != nil { + addressProperties, diag := flattenIP4AddressProperties(ip4Address) + if diag.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.AddError("Error parsing the host record properties", err.Error()) + resp.Diagnostics.Append(diag...) + return } - data.Address = addressProperties.address - data.State = addressProperties.state - data.MACAddress = addressProperties.macAddress - data.CustomProperties = addressProperties.customProperties + data.Address = addressProperties.Address + data.State = addressProperties.State + data.MACAddress = addressProperties.MACAddress + data.RouterPortInfo = addressProperties.RouterPortInfo + data.SwitchPortInfo = addressProperties.SwitchPortInfo + data.VLANInfo = addressProperties.VLANInfo + data.LeaseTime = addressProperties.LeaseTime + data.ExpiryTime = addressProperties.ExpiryTime + data.ParameterRequestList = addressProperties.ParameterRequestList + data.VendorClassIdentifier = addressProperties.VendorClassIdentifier + data.LocationCode = addressProperties.LocationCode + data.LocationInherited = addressProperties.LocationInherited + data.UserDefinedFields = addressProperties.UserDefinedFields resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } - -type ip4AddressProperties struct { - address types.String - state types.String - macAddress types.String - customProperties types.Map -} - -func parseIP4AddressProperties(properties string) (ip4AddressProperties, error) { - var ip4Properties ip4AddressProperties - cpMap := make(map[string]attr.Value) - - props := strings.Split(properties, "|") - for x := range props { - if len(props[x]) > 0 { - prop := strings.Split(props[x], "=")[0] - val := strings.Split(props[x], "=")[1] - - switch prop { - case "address": - ip4Properties.address = types.StringValue(val) - case "state": - ip4Properties.state = types.StringValue(val) - case "macAddress": - ip4Properties.macAddress = types.StringValue(val) - default: - cpMap[prop] = types.StringValue(val) - } - } - } - - customProperties, diag := types.MapValue(types.StringType, cpMap) - if diag.HasError() { - return ip4Properties, fmt.Errorf("error creating custom properties map") - } - ip4Properties.customProperties = customProperties - return ip4Properties, nil -} diff --git a/internal/provider/resource_ip4_address.go b/internal/provider/resource_ip4_address.go index f94285a..eeea351 100644 --- a/internal/provider/resource_ip4_address.go +++ b/internal/provider/resource_ip4_address.go @@ -31,19 +31,35 @@ type IP4AddressResource struct { client *loginClient } -// ExampleResourceModel describes the resource data model. +// IP4AddressResourceModel describes the resource data model. type IP4AddressResourceModel struct { - ID types.Int64 `tfsdk:"id"` - ConfigurationID types.Int64 `tfsdk:"configuration_id"` - Name types.String `tfsdk:"name"` - ParentID types.Int64 `tfsdk:"parent_id"` - Action types.String `tfsdk:"action"` - CustomProperties types.Map `tfsdk:"custom_properties"` - MACAddress types.String `tfsdk:"mac_address"` - Address types.String `tfsdk:"address"` - Properties types.String `tfsdk:"properties"` - State types.String `tfsdk:"state"` - Type types.String `tfsdk:"type"` + // These are exposed for a generic entity object in bluecat + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Properties types.String `tfsdk:"properties"` + + // These are exposed via the entity properties field for objects of type IP4Address + Address types.String `tfsdk:"address"` + State types.String `tfsdk:"state"` + MACAddress types.String `tfsdk:"mac_address"` + RouterPortInfo types.String `tfsdk:"router_port_info"` + SwitchPortInfo types.String `tfsdk:"switch_port_info"` + VLANInfo types.String `tfsdk:"vlan_info"` + LeaseTime types.String `tfsdk:"lease_time"` + ExpiryTime types.String `tfsdk:"expiry_time"` + ParameterRequestList types.String `tfsdk:"parameter_request_list"` + VendorClassIdentifier types.String `tfsdk:"vendor_class_identifier"` + LocationCode types.String `tfsdk:"location_code"` + LocationInherited types.Bool `tfsdk:"location_inherited"` + + // these are user defined fields that are not built-in + UserDefinedFields types.Map `tfsdk:"user_defined_fields"` + + // These fields are only used for creation + Action types.String `tfsdk:"action"` + ConfigurationID types.Int64 `tfsdk:"configuration_id"` + ParentID types.Int64 `tfsdk:"parent_id"` } func (r *IP4AddressResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -56,13 +72,39 @@ func (r *IP4AddressResource) Schema(ctx context.Context, req resource.SchemaRequ MarkdownDescription: "Resource to reserve an IPv4 address.", Attributes: map[string]schema.Attribute{ + // These are exposed for Entity objects via the API "id": schema.Int64Attribute{ + MarkdownDescription: "IPv4 Address identifier.", Computed: true, - MarkdownDescription: "IP4 address identifier", PlanModifiers: []planmodifier.Int64{ int64planmodifier.UseStateForUnknown(), }, }, + "name": schema.StringAttribute{ + MarkdownDescription: "The display name of the IPv4 address.", + Optional: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the resource.", + Computed: true, + }, + "properties": schema.StringAttribute{ + MarkdownDescription: "The properties of the resource as returned by the API (pipe delimited).", + Computed: true, + }, + // These fields are only used for creation and are not exposed via the API entity + "action": schema.StringAttribute{ + MarkdownDescription: "The action to take on the next available IPv4 address. Must be one of: \"MAKE_STATIC\", \"MAKE_RESERVED\", or \"MAKE_DHCP_RESERVED\". If changed, forces a new resource.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("MAKE_STATIC"), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf(gobam.IPAssignmentActions...), + }, + }, "configuration_id": schema.Int64Attribute{ MarkdownDescription: "The object ID of the Configuration that will hold the new address. If changed, forces a new resource.", Required: true, @@ -70,10 +112,6 @@ func (r *IP4AddressResource) Schema(ctx context.Context, req resource.SchemaRequ int64planmodifier.RequiresReplace(), }, }, - "name": schema.StringAttribute{ - MarkdownDescription: "The name assigned to the IPv4 address. This is not related to DNS.", - Required: true, - }, "parent_id": schema.Int64Attribute{ MarkdownDescription: "The object ID of the Configuration, Block, or Network to find the next available IPv4 address in. If changed, forces a new resource.", Required: true, @@ -81,45 +119,65 @@ func (r *IP4AddressResource) Schema(ctx context.Context, req resource.SchemaRequ int64planmodifier.RequiresReplace(), }, }, - "action": schema.StringAttribute{ - MarkdownDescription: "The action to take on the next available IPv4 address. Must be one of: \"MAKE_STATIC\", \"MAKE_RESERVED\", or \"MAKE_DHCP_RESERVED\". If changed, forces a new resource.", - Optional: true, + // These are exposed via the API properties field for objects of type IP4Address + "address": schema.StringAttribute{ + MarkdownDescription: "The IPv4 address that was allocated.", Computed: true, - Default: stringdefault.StaticString("MAKE_STATIC"), - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.OneOf(gobam.IPAssignmentActions...), - }, }, - "custom_properties": schema.MapAttribute{ - MarkdownDescription: "A map of all custom properties associated with the IPv4 address.", - Optional: true, + "state": schema.StringAttribute{ + MarkdownDescription: "The state of the IPv4 address.", Computed: true, - ElementType: types.StringType, }, "mac_address": schema.StringAttribute{ MarkdownDescription: "The MAC address to associate with the IPv4 address.", Optional: true, + }, + "router_port_info": schema.StringAttribute{ + MarkdownDescription: "Connected router port information of the IPv4 address.", Computed: true, - Default: stringdefault.StaticString(""), }, - "address": schema.StringAttribute{ - MarkdownDescription: "The IPv4 address that was allocated.", + "switch_port_info": schema.StringAttribute{ + MarkdownDescription: "Connected switch port information of the IPv4 address.", Computed: true, }, - "properties": schema.StringAttribute{ - MarkdownDescription: "The properties of the IPv4 address as returned by the API (pipe delimited).", + "vlan_info": schema.StringAttribute{ + MarkdownDescription: "VLAN information of the IPv4 address.", Computed: true, }, - "state": schema.StringAttribute{ - MarkdownDescription: "The state of the IPv4 address.", + "lease_time": schema.StringAttribute{ + MarkdownDescription: "Time that IPv4 address was leased.", Computed: true, }, - "type": schema.StringAttribute{ - MarkdownDescription: "The type of the resource.", + "expiry_time": schema.StringAttribute{ + MarkdownDescription: "Time that IPv4 address lease expires.", + Computed: true, + }, + "parameter_request_list": schema.StringAttribute{ + MarkdownDescription: "Time that IPv4 address lease expires.", + Computed: true, + }, + "vendor_class_identifier": schema.StringAttribute{ + MarkdownDescription: "Time that IPv4 address lease expires.", + Computed: true, + }, + "location_code": schema.StringAttribute{ + MarkdownDescription: "The location code of the address.", + Computed: true, + Optional: true, + Default: nil, + Validators: []validator.String{ + // The code is case-sensitive and must be in uppercase letters. The country code and child location code should be alphanumeric strings. + }, + }, + "location_inherited": schema.BoolAttribute{ + MarkdownDescription: "The location is inherited.", + Computed: true, + }, + "user_defined_fields": schema.MapAttribute{ + MarkdownDescription: "A map of all user-definied fields associated with the IPv4 address.", Computed: true, + Optional: true, + ElementType: types.StringType, }, }, } @@ -168,9 +226,15 @@ func (r *IP4AddressResource) Create(ctx context.Context, req resource.CreateRequ action := data.Action.ValueString() name := data.Name.ValueString() properties := "name=" + name + "|" - customProperties := data.CustomProperties.Elements() - for k, v := range customProperties { - properties = properties + k + "=" + v.String() + "|" + + if !data.LocationCode.IsUnknown() && !data.LocationCode.IsNull() { + properties = properties + fmt.Sprintf("locationCode=%s|", data.LocationCode.ValueString()) + } + + var udfs map[string]string + data.UserDefinedFields.ElementsAs(ctx, &udfs, false) + for k, v := range udfs { + properties = properties + k + "=" + v + "|" } ip, err := client.AssignNextAvailableIP4Address(configID, parentID, macAddress, hostInfo, action, properties) @@ -182,10 +246,43 @@ func (r *IP4AddressResource) Create(ctx context.Context, req resource.CreateRequ data.ID = types.Int64PointerValue(ip.Id) + entity, err := client.GetEntityById(data.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.AddError( + "Failed to get IP4 Address by Id after creation", + err.Error(), + ) + return + } + + data.Name = types.StringPointerValue(entity.Name) + data.Properties = types.StringPointerValue(entity.Properties) + data.Type = types.StringPointerValue(entity.Type) + + addressProperties, diag := flattenIP4AddressProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + + data.Address = addressProperties.Address + data.State = addressProperties.State + data.MACAddress = addressProperties.MACAddress + data.RouterPortInfo = addressProperties.RouterPortInfo + data.SwitchPortInfo = addressProperties.SwitchPortInfo + data.VLANInfo = addressProperties.VLANInfo + data.LeaseTime = addressProperties.LeaseTime + data.ExpiryTime = addressProperties.ExpiryTime + data.ParameterRequestList = addressProperties.ParameterRequestList + data.VendorClassIdentifier = addressProperties.VendorClassIdentifier + data.LocationCode = addressProperties.LocationCode + data.LocationInherited = addressProperties.LocationInherited + data.UserDefinedFields = addressProperties.UserDefinedFields + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log tflog.Trace(ctx, "created a resource") // Save data into Terraform state @@ -208,8 +305,7 @@ func (r *IP4AddressResource) Read(ctx context.Context, req resource.ReadRequest, return } - id := data.ID.ValueInt64() - entity, err := client.GetEntityById(id) + entity, err := client.GetEntityById(data.ID.ValueInt64()) if err != nil { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) resp.Diagnostics.AddError("Failed to get IP4 Address by Id", err.Error()) @@ -226,17 +322,26 @@ func (r *IP4AddressResource) Read(ctx context.Context, req resource.ReadRequest, data.Properties = types.StringPointerValue(entity.Properties) data.Type = types.StringPointerValue(entity.Type) - addressProperties, err := parseIP4AddressProperties(*entity.Properties) - if err != nil { + addressProperties, diag := flattenIP4AddressProperties(entity) + if diag.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.AddError("Failed to parse IP4 Address properties", err.Error()) + resp.Diagnostics.Append(diag...) return } - data.Address = addressProperties.address - data.State = addressProperties.state - data.MACAddress = addressProperties.macAddress - data.CustomProperties = addressProperties.customProperties + data.Address = addressProperties.Address + data.State = addressProperties.State + data.MACAddress = addressProperties.MACAddress + data.RouterPortInfo = addressProperties.RouterPortInfo + data.SwitchPortInfo = addressProperties.SwitchPortInfo + data.VLANInfo = addressProperties.VLANInfo + data.LeaseTime = addressProperties.LeaseTime + data.ExpiryTime = addressProperties.ExpiryTime + data.ParameterRequestList = addressProperties.ParameterRequestList + data.VendorClassIdentifier = addressProperties.VendorClassIdentifier + data.LocationCode = addressProperties.LocationCode + data.LocationInherited = addressProperties.LocationInherited + data.UserDefinedFields = addressProperties.UserDefinedFields resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) @@ -245,10 +350,11 @@ func (r *IP4AddressResource) Read(ctx context.Context, req resource.ReadRequest, } func (r *IP4AddressResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data *IP4AddressResourceModel + var data, state *IP4AddressResourceModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return @@ -260,26 +366,30 @@ func (r *IP4AddressResource) Update(ctx context.Context, req resource.UpdateRequ return } - id := data.ID.ValueInt64() - macAddress := data.MACAddress.ValueString() - name := data.Name.ValueString() - otype := data.Type.ValueString() - properties := "name=" + name + "|" + properties := "" - if macAddress != "" { - properties = properties + "macAddress=" + macAddress + "|" + if !data.MACAddress.Equal(state.MACAddress) { + properties = properties + fmt.Sprintf("macAddress=%s|", data.MACAddress.ValueString()) } - customProperties := data.CustomProperties.Elements() - for k, v := range customProperties { - properties = properties + k + "=" + v.String() + "|" + if !data.LocationCode.Equal(state.LocationCode) { + properties = properties + fmt.Sprintf("locationCode=%s|", data.LocationCode.ValueString()) + } + + if !data.UserDefinedFields.Equal(state.UserDefinedFields) { + var udfs map[string]string + resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, &udfs, false)...) + + for k, v := range udfs { + properties = properties + fmt.Sprintf("%s=%s|", k, v) + } } update := gobam.APIEntity{ - Id: &id, - Name: &name, + Id: data.ID.ValueInt64Pointer(), + Name: data.Name.ValueStringPointer(), Properties: &properties, - Type: &otype, + Type: state.Type.ValueStringPointer(), } err := client.Update(&update) @@ -289,6 +399,41 @@ func (r *IP4AddressResource) Update(ctx context.Context, req resource.UpdateRequ return } + entity, err := client.GetEntityById(data.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.AddError( + "Failed to get IP4 Address by Id after creation", + err.Error(), + ) + return + } + + data.Name = types.StringPointerValue(entity.Name) + data.Properties = types.StringPointerValue(entity.Properties) + data.Type = types.StringPointerValue(entity.Type) + + addressProperties, diag := flattenIP4AddressProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + + data.Address = addressProperties.Address + data.State = addressProperties.State + data.MACAddress = addressProperties.MACAddress + data.RouterPortInfo = addressProperties.RouterPortInfo + data.SwitchPortInfo = addressProperties.SwitchPortInfo + data.VLANInfo = addressProperties.VLANInfo + data.LeaseTime = addressProperties.LeaseTime + data.ExpiryTime = addressProperties.ExpiryTime + data.ParameterRequestList = addressProperties.ParameterRequestList + data.VendorClassIdentifier = addressProperties.VendorClassIdentifier + data.LocationCode = addressProperties.LocationCode + data.LocationInherited = addressProperties.LocationInherited + data.UserDefinedFields = addressProperties.UserDefinedFields + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) // Save updated data into Terraform state From 313a95700f0ffb6f4d17d162791362ec21570a20 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 09:37:38 -0400 Subject: [PATCH 09/15] add flattenHostRecordProperties func --- internal/provider/common.go | 138 ++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/internal/provider/common.go b/internal/provider/common.go index 46317f9..51c3a92 100644 --- a/internal/provider/common.go +++ b/internal/provider/common.go @@ -319,3 +319,141 @@ func flattenIP4AddressProperties(e *gobam.APIEntity) (*IP4AddressModel, diag.Dia i.UserDefinedFields = userDefinedFields return i, d } + +// HostRecordModel describes the data model the built-in properties for a Host Record object. +type HostRecordModel struct { + // These are exposed via the entity properties field for objects of type IP4Network + TTL types.Int64 + AbsoluteName types.String + Addresses types.Set + ReverseRecord types.Bool + + // these are user defined fields that are not built-in + UserDefinedFields types.Map + + // these are returned by the API but do not appear in the documentation + AddressIDs types.Set + + // these are returned by the API with a hint based search but do not appear in the documentation + ParentID types.Int64 + ParentType types.String +} + +func flattenHostRecordProperties(e *gobam.APIEntity) (*HostRecordModel, diag.Diagnostics) { + var d diag.Diagnostics + + if e == nil { + d.AddError("invalid input to flattenHostRecordProperties", "entity passed was nil") + return nil, d + } + if e.Type == nil { + d.AddError("invalid input to flattenHostRecordProperties", "type of entity passed was nil") + return nil, d + } else if *e.Type != "HostRecord" { + d.AddError("invalid input to flattenHostRecordProperties", fmt.Sprintf("type of entity passed was %s", *e.Type)) + return nil, d + } + + h := &HostRecordModel{} + udfMap := make(map[string]attr.Value) + + addressesFound := false + addressIDsFound := false + var ttl int64 = -1 + var addressesSet basetypes.SetValue + var addressIDsSet basetypes.SetValue + + if e.Properties != nil { + props := strings.Split(*e.Properties, "|") + for x := range props { + if len(props[x]) > 0 { + prop := strings.Split(props[x], "=")[0] + val := strings.Split(props[x], "=")[1] + + switch prop { + case "ttl": + t, err := strconv.ParseInt(val, 10, 64) + if err != nil { + d.AddError("error parsing ttl to int64", err.Error()) + break + } + ttl = t + case "absoluteName": + h.AbsoluteName = types.StringValue(val) + case "addresses": + addressesFound = true + var aDiag diag.Diagnostics + addresses := strings.Split(val, ",") + addressesList := []attr.Value{} + for x := range addresses { + addressesList = append(addressesList, types.StringValue(addresses[x])) + } + + addressesSet, aDiag = basetypes.NewSetValue(types.StringType, addressesList) + if aDiag.HasError() { + d.Append(aDiag...) + break + } + case "addressIds": + addressIDsFound = true + var aDiag diag.Diagnostics + addressIDs := strings.Split(val, ",") + addressIDsList := []attr.Value{} + for x := range addressIDs { + addressID, err := strconv.ParseInt(addressIDs[x], 10, 64) + if err != nil { + d.AddError("error parsing addressIds to int64", err.Error()) + break + } + addressIDsList = append(addressIDsList, types.Int64Value(addressID)) + } + addressIDsSet, aDiag = basetypes.NewSetValue(types.Int64Type, addressIDsList) + if aDiag.HasError() { + d.Append(aDiag...) + break + } + case "parentId": + pid, err := strconv.ParseInt(val, 10, 64) + if err != nil { + d.AddError("error parsing parentId to int64", err.Error()) + break + } + h.ParentID = types.Int64Value(pid) + case "parentType": + h.ParentType = types.StringValue(val) + case "reverseRecord": + b, err := strconv.ParseBool(val) + if err != nil { + d.AddError("error parsing reverseRecord to bool", err.Error()) + break + } + h.ReverseRecord = types.BoolValue(b) + default: + udfMap[prop] = types.StringValue(val) + } + } + } + } + + if !addressesFound { + addressesSet = basetypes.NewSetNull(types.StringType) + } + h.Addresses = addressesSet + + if !addressIDsFound { + addressIDsSet = basetypes.NewSetNull(types.Int64Type) + } + h.AddressIDs = addressIDsSet + + h.TTL = types.Int64Value(ttl) + + var userDefinedFields basetypes.MapValue + var udfDiag diag.Diagnostics + userDefinedFields, udfDiag = basetypes.NewMapValue(types.StringType, udfMap) + if udfDiag.HasError() { + d.Append(udfDiag...) + } + h.UserDefinedFields = userDefinedFields + + return h, d +} From 29a06413b4636d41f4108740792a846d5da4f42b Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 09:38:42 -0400 Subject: [PATCH 10/15] fix host record data source to use new func --- internal/provider/data_source_host_record.go | 146 +++---------------- 1 file changed, 24 insertions(+), 122 deletions(-) diff --git a/internal/provider/data_source_host_record.go b/internal/provider/data_source_host_record.go index 66c3e2f..d97dc36 100644 --- a/internal/provider/data_source_host_record.go +++ b/internal/provider/data_source_host_record.go @@ -6,12 +6,9 @@ import ( "strconv" "strings" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" ) @@ -29,18 +26,18 @@ type HostRecordDataSource struct { // HostRecordDataSourceModel describes the data source data model. type HostRecordDataSourceModel struct { - ID types.Int64 `tfsdk:"id"` - AbsoluteName types.String `tfsdk:"absolute_name"` - Addresses types.Set `tfsdk:"addresses"` - AddressIDs types.Set `tfsdk:"address_ids"` - CustomProperties types.Map `tfsdk:"custom_properties"` - Name types.String `tfsdk:"name"` - ParentID types.Int64 `tfsdk:"parent_id"` - ParentType types.String `tfsdk:"parent_type"` - Properties types.String `tfsdk:"properties"` - ReverseRecord types.Bool `tfsdk:"reverse_record"` - TTL types.Int64 `tfsdk:"ttl"` - Type types.String `tfsdk:"type"` + ID types.Int64 `tfsdk:"id"` + AbsoluteName types.String `tfsdk:"absolute_name"` + Addresses types.Set `tfsdk:"addresses"` + AddressIDs types.Set `tfsdk:"address_ids"` + UserDefinedFields types.Map `tfsdk:"user_defined_fields"` + Name types.String `tfsdk:"name"` + ParentID types.Int64 `tfsdk:"parent_id"` + ParentType types.String `tfsdk:"parent_type"` + Properties types.String `tfsdk:"properties"` + ReverseRecord types.Bool `tfsdk:"reverse_record"` + TTL types.Int64 `tfsdk:"ttl"` + Type types.String `tfsdk:"type"` } func (d *HostRecordDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -71,7 +68,7 @@ func (d *HostRecordDataSource) Schema(ctx context.Context, req datasource.Schema Computed: true, ElementType: types.Int64Type, }, - "custom_properties": schema.MapAttribute{ + "user_defined_fields": schema.MapAttribute{ MarkdownDescription: "A map of all custom properties associated with the host record.", Computed: true, ElementType: types.StringType, @@ -201,20 +198,21 @@ func (d *HostRecordDataSource) Read(ctx context.Context, req datasource.ReadRequ data.Properties = types.StringValue(*hostRecords.Item[matchLocation].Properties) data.Type = types.StringValue(*hostRecords.Item[matchLocation].Type) - hostRecordProperties := parseHostRecordProperties(*hostRecords.Item[matchLocation].Properties, resp.Diagnostics) - if resp.Diagnostics.HasError() { + hostRecordProperties, diag := flattenHostRecordProperties(hostRecords.Item[matchLocation]) + if diag.HasError() { + resp.Diagnostics.Append(diag...) resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) return } - data.AbsoluteName = hostRecordProperties.absoluteName - data.ParentID = hostRecordProperties.parentID - data.ParentType = hostRecordProperties.parentType - data.ReverseRecord = hostRecordProperties.reverseRecord - data.Addresses = hostRecordProperties.addresses - data.AddressIDs = hostRecordProperties.addressIDs - data.CustomProperties = hostRecordProperties.customProperties - data.TTL = hostRecordProperties.ttl + data.AbsoluteName = hostRecordProperties.AbsoluteName + data.ParentID = hostRecordProperties.ParentID + data.ParentType = hostRecordProperties.ParentType + data.ReverseRecord = hostRecordProperties.ReverseRecord + data.Addresses = hostRecordProperties.Addresses + data.AddressIDs = hostRecordProperties.AddressIDs + data.UserDefinedFields = hostRecordProperties.UserDefinedFields + data.TTL = hostRecordProperties.TTL resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) @@ -225,99 +223,3 @@ func (d *HostRecordDataSource) Read(ctx context.Context, req datasource.ReadRequ // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } - -type hostRecordProperties struct { - absoluteName types.String - parentID types.Int64 - parentType types.String - ttl types.Int64 - reverseRecord types.Bool - addresses types.Set - addressIDs types.Set - customProperties types.Map -} - -func parseHostRecordProperties(properties string, diag diag.Diagnostics) hostRecordProperties { - var hrProperties hostRecordProperties - - cpMap := make(map[string]attr.Value) - - // if ttl isn't returned as a property it will remain set at -1 - hrProperties.ttl = types.Int64Value(-1) - - props := strings.Split(properties, "|") - for x := range props { - if len(props[x]) > 0 { - prop := strings.Split(props[x], "=")[0] - val := strings.Split(props[x], "=")[1] - - switch prop { - case "absoluteName": - hrProperties.absoluteName = types.StringValue(val) - case "parentId": - pID, err := strconv.ParseInt(val, 10, 64) - if err != nil { - diag.AddError("error parsing parentId to int64", fmt.Sprintf("value of parentId was %s", val)) - return hrProperties - } - hrProperties.parentID = types.Int64Value(pID) - case "parentType": - hrProperties.parentType = types.StringValue(val) - case "reverseRecord": - b, err := strconv.ParseBool(val) - if err != nil { - diag.AddError("error parsing reverseRecord to bool", fmt.Sprintf("value of reverseRecord was %s", val)) - return hrProperties - } - hrProperties.reverseRecord = types.BoolValue(b) - case "addresses": - addresses := strings.Split(val, ",") - addressList := []attr.Value{} - for i := range addresses { - addressList = append(addressList, types.StringValue(addresses[i])) - } - var addressSet basetypes.SetValue - addressSet, diag = types.SetValue(types.StringType, addressList) - if diag.HasError() { - return hrProperties - } - hrProperties.addresses = addressSet - case "addressIds": - addressIDs := strings.Split(val, ",") - aidList := []attr.Value{} - for i := range addressIDs { - aID, err := strconv.ParseInt(addressIDs[i], 10, 64) - if err != nil { - diag.AddError("error parsing addressIds to int64", fmt.Sprintf("value in addressIds was %s", val)) - return hrProperties - } - aidList = append(aidList, types.Int64Value(aID)) - } - var aidSet basetypes.SetValue - aidSet, diag = basetypes.NewSetValue(types.Int64Type, aidList) - if diag.HasError() { - return hrProperties - } - hrProperties.addressIDs = aidSet - case "ttl": - ttlval, err := strconv.ParseInt(val, 10, 64) - if err != nil { - diag.AddError("error parsing ttl to int", fmt.Sprintf("value in ttl was %s", val)) - return hrProperties - } - hrProperties.ttl = types.Int64Value(ttlval) - default: - cpMap[prop] = types.StringValue(val) - } - } - } - - var customProperties basetypes.MapValue - customProperties, diag = basetypes.NewMapValue(types.StringType, cpMap) - if diag.HasError() { - return hrProperties - } - hrProperties.customProperties = customProperties - - return hrProperties -} From 5f9efa3fcd5f3c3a7b388ac19aa16e76167ea274 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 09:47:15 -0400 Subject: [PATCH 11/15] fix host record resource --- internal/provider/resource_host_record.go | 232 +++++++++++++++------- 1 file changed, 157 insertions(+), 75 deletions(-) diff --git a/internal/provider/resource_host_record.go b/internal/provider/resource_host_record.go index be2df53..467b547 100644 --- a/internal/provider/resource_host_record.go +++ b/internal/provider/resource_host_record.go @@ -12,10 +12,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/umich-vci/gobam" ) @@ -33,20 +34,29 @@ type HostRecordResource struct { client *loginClient } -// ExampleResourceModel describes the resource data model. +// HostRecordResourceModel describes the resource data model. type HostRecordResourceModel struct { - ID types.Int64 `tfsdk:"id"` - Addresses types.Set `tfsdk:"addresses"` - DNSZone types.String `tfsdk:"dns_zone"` - Name types.String `tfsdk:"name"` - ViewID types.Int64 `tfsdk:"view_id"` - Comments types.String `tfsdk:"comments"` - CustomProperties types.Map `tfsdk:"custom_properties"` - ReverseRecord types.Bool `tfsdk:"reverse_record"` - TTL types.Int64 `tfsdk:"ttl"` - AbsoluteName types.String `tfsdk:"absolute_name"` - Properties types.String `tfsdk:"properties"` - Type types.String `tfsdk:"type"` + // These are exposed for a generic entity object in bluecat + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Properties types.String `tfsdk:"properties"` + + // These are exposed via the entity properties field for objects of type IP4Address + TTL types.Int64 `tfsdk:"ttl"` + AbsoluteName types.String `tfsdk:"absolute_name"` + Addresses types.Set `tfsdk:"addresses"` + ReverseRecord types.Bool `tfsdk:"reverse_record"` + + // this is returned by the API but do not appear in the documentation + AddressIDs types.Set `tfsdk:"address_ids"` + + // these are user defined fields that are not built-in + UserDefinedFields types.Map `tfsdk:"user_defined_fields"` + + // These fields are only used for creation + DNSZone types.String `tfsdk:"dns_zone"` + ViewID types.Int64 `tfsdk:"view_id"` } func (r *HostRecordResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -59,18 +69,27 @@ func (r *HostRecordResource) Schema(ctx context.Context, req resource.SchemaRequ MarkdownDescription: "Resource create a host record.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ + // These are exposed for Entity objects via the API + "id": schema.Int64Attribute{ Computed: true, MarkdownDescription: "Host Record identifier", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), }, }, - "addresses": schema.SetAttribute{ - MarkdownDescription: "The address(es) to be associated with the host record.", + "name": schema.StringAttribute{ + MarkdownDescription: "The name of the host record to be created. Combined with `dns_zone` to make the fqdn.", Required: true, - ElementType: types.StringType, }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the resource.", + Computed: true, + }, + "properties": schema.StringAttribute{ + MarkdownDescription: "The properties of the host record as returned by the API (pipe delimited).", + Computed: true, + }, + // These fields are only used for creation and are not exposed via the API entity "dns_zone": schema.StringAttribute{ MarkdownDescription: "The DNS zone to create the host record in. Combined with `name` to make the fqdn. If changed, forces a new resource.", Required: true, @@ -78,10 +97,6 @@ func (r *HostRecordResource) Schema(ctx context.Context, req resource.SchemaRequ stringplanmodifier.RequiresReplace(), }, }, - "name": schema.StringAttribute{ - MarkdownDescription: "The name of the host record to be created. Combined with `dns_zone` to make the fqdn.", - Required: true, - }, "view_id": schema.Int64Attribute{ MarkdownDescription: "The object ID of the View that host record should be created in. If changed, forces a new resource.", Required: true, @@ -89,17 +104,16 @@ func (r *HostRecordResource) Schema(ctx context.Context, req resource.SchemaRequ int64planmodifier.RequiresReplace(), }, }, - "comments": schema.StringAttribute{ - MarkdownDescription: "Comments to be associated with the host record.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(""), + // These are exposed via the API properties field for objects of type Host Record + "addresses": schema.SetAttribute{ + MarkdownDescription: "The address(es) to be associated with the host record.", + Required: true, + ElementType: types.StringType, }, - "custom_properties": schema.MapAttribute{ - MarkdownDescription: "A map of all custom properties associated with the host record.", - Optional: true, + "address_ids": schema.SetAttribute{ + MarkdownDescription: "A set of all address ids associated with the host record.", Computed: true, - ElementType: types.StringType, + ElementType: types.Int64Type, }, "reverse_record": schema.BoolAttribute{ MarkdownDescription: "If a reverse record should be created for addresses.", @@ -117,13 +131,12 @@ func (r *HostRecordResource) Schema(ctx context.Context, req resource.SchemaRequ MarkdownDescription: "The absolute name (fqdn) of the host record.", Computed: true, }, - "properties": schema.StringAttribute{ - MarkdownDescription: "The properties of the host record as returned by the API (pipe delimited).", - Computed: true, - }, - "type": schema.StringAttribute{ - MarkdownDescription: "The type of the resource.", + "user_defined_fields": schema.MapAttribute{ + MarkdownDescription: "A map of all user-definied fields associated with the Host Record.", + Optional: true, Computed: true, + ElementType: types.StringType, + Default: mapdefault.StaticValue(basetypes.NewMapValueMust(types.StringType, nil)), }, }, } @@ -169,21 +182,26 @@ func (r *HostRecordResource) Create(ctx context.Context, req resource.CreateRequ absoluteName := data.Name.ValueString() + "." + data.DNSZone.ValueString() ttl := data.TTL.ValueInt64() - addresses := []string{} - diag = data.Addresses.ElementsAs(ctx, addresses, false) + var addresses []string + diag = data.Addresses.ElementsAs(ctx, &addresses, false) if diag.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) resp.Diagnostics.Append(diag...) return } - reverseRecord := strconv.FormatBool(data.ReverseRecord.ValueBool()) - comments := data.Comments.ValueString() - properties := "reverseRecord=" + reverseRecord + "|comments=" + comments + "|" + properties := "" + properties = properties + fmt.Sprintf("reverseRecord=%s|", strconv.FormatBool(data.ReverseRecord.ValueBool())) - customProperties := data.CustomProperties.Elements() - for k, v := range customProperties { - properties = properties + k + "=" + v.String() + "|" + var udfs map[string]string + resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, &udfs, false)...) + if resp.Diagnostics.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + for k, v := range udfs { + properties = properties + fmt.Sprintf("%s=%s|", k, v) } host, err := client.AddHostRecord(viewID, absoluteName, strings.Join(addresses, ","), ttl, properties) @@ -195,6 +213,34 @@ func (r *HostRecordResource) Create(ctx context.Context, req resource.CreateRequ data.ID = types.Int64Value(host) + entity, err := client.GetEntityById(data.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.AddError( + "Failed to get IP4 Address by Id after creation", + err.Error(), + ) + return + } + + data.Name = types.StringPointerValue(entity.Name) + data.Properties = types.StringPointerValue(entity.Properties) + data.Type = types.StringPointerValue(entity.Type) + + hrProperties, diag := flattenHostRecordProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + + data.AbsoluteName = hrProperties.AbsoluteName + data.Addresses = hrProperties.Addresses + data.AddressIDs = hrProperties.AddressIDs + data.TTL = hrProperties.TTL + data.ReverseRecord = hrProperties.ReverseRecord + data.UserDefinedFields = hrProperties.UserDefinedFields + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) // Write logs using the tflog package @@ -240,17 +286,19 @@ func (r *HostRecordResource) Read(ctx context.Context, req resource.ReadRequest, data.Properties = types.StringPointerValue(entity.Properties) data.Type = types.StringPointerValue(entity.Type) - hostRecordProperties := parseHostRecordProperties(*entity.Properties, resp.Diagnostics) - if resp.Diagnostics.HasError() { + hostRecordProperties, diag := flattenHostRecordProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(diag...) resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) return } - data.AbsoluteName = hostRecordProperties.absoluteName - data.Addresses = hostRecordProperties.addresses - data.CustomProperties = hostRecordProperties.customProperties - data.ReverseRecord = hostRecordProperties.reverseRecord - data.TTL = hostRecordProperties.ttl + data.AbsoluteName = hostRecordProperties.AbsoluteName + data.Addresses = hostRecordProperties.Addresses + data.AddressIDs = hostRecordProperties.AddressIDs + data.ReverseRecord = hostRecordProperties.ReverseRecord + data.TTL = hostRecordProperties.TTL + data.UserDefinedFields = hostRecordProperties.UserDefinedFields resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) @@ -259,10 +307,11 @@ func (r *HostRecordResource) Read(ctx context.Context, req resource.ReadRequest, } func (r *HostRecordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data *HostRecordResourceModel + var data, state *HostRecordResourceModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return @@ -274,36 +323,41 @@ func (r *HostRecordResource) Update(ctx context.Context, req resource.UpdateRequ return } - id := data.ID.ValueInt64() - name := data.Name.ValueString() - otype := data.Type.ValueString() - ttl := strconv.FormatInt(data.TTL.ValueInt64(), 10) + var addresses []string + resp.Diagnostics.Append(data.Addresses.ElementsAs(ctx, &addresses, false)...) - addresses := []string{} - diag = data.Addresses.ElementsAs(ctx, addresses, false) - if diag.HasError() { + var udfs map[string]string + resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, udfs, false)...) + + if resp.Diagnostics.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) - resp.Diagnostics.AddError( - "Parsing addresses failed", - "", - ) + resp.Diagnostics.Append(diag...) return } - reverseRecord := strconv.FormatBool(data.ReverseRecord.ValueBool()) - comments := data.Comments.ValueString() - properties := "reverseRecord=" + reverseRecord + "|comments=" + comments + "|ttl=" + ttl + "|addresses=" + strings.Join(addresses, ",") + "|" + properties := "" + + if !data.Addresses.Equal(state.Addresses) { + properties = properties + fmt.Sprintf("addresses=%s|", strings.Join(addresses, ",")) + } + + if !data.ReverseRecord.Equal(state.ReverseRecord) { + properties = properties + fmt.Sprintf("reverseRecord=%s|", strconv.FormatBool(data.ReverseRecord.ValueBool())) + } + + if !data.TTL.Equal(state.TTL) { + properties = properties + fmt.Sprintf("ttl=%d|", data.TTL.ValueInt64()) + } - customProperties := data.CustomProperties.Elements() - for k, v := range customProperties { - properties = properties + k + "=" + v.String() + "|" + for k, v := range udfs { + properties = properties + fmt.Sprintf("%s=%s|", k, v) } update := gobam.APIEntity{ - Id: &id, - Name: &name, + Id: data.ID.ValueInt64Pointer(), + Name: data.Name.ValueStringPointer(), Properties: &properties, - Type: &otype, + Type: state.Type.ValueStringPointer(), } err := client.Update(&update) @@ -313,6 +367,34 @@ func (r *HostRecordResource) Update(ctx context.Context, req resource.UpdateRequ return } + entity, err := client.GetEntityById(data.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.AddError( + "Failed to get host record by Id after update", + err.Error(), + ) + return + } + + data.Name = types.StringPointerValue(entity.Name) + data.Properties = types.StringPointerValue(entity.Properties) + data.Type = types.StringPointerValue(entity.Type) + + hrProperties, diag := flattenHostRecordProperties(entity) + if diag.HasError() { + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) + resp.Diagnostics.Append(diag...) + return + } + + data.AbsoluteName = hrProperties.AbsoluteName + data.Addresses = hrProperties.Addresses + data.AddressIDs = hrProperties.AddressIDs + data.TTL = hrProperties.TTL + data.ReverseRecord = hrProperties.ReverseRecord + data.UserDefinedFields = hrProperties.UserDefinedFields + resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) // Save updated data into Terraform state From 542f9e8719aa0ed16a5d5b708a2646b5baedc047 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 10:17:32 -0400 Subject: [PATCH 12/15] fix UDF removal --- internal/provider/resource_host_record.go | 36 ++++++++++++++++------- internal/provider/resource_ip4_address.go | 17 ++++++++++- internal/provider/resource_ip4_network.go | 31 +++++++++++++++++++ 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/internal/provider/resource_host_record.go b/internal/provider/resource_host_record.go index 467b547..1297303 100644 --- a/internal/provider/resource_host_record.go +++ b/internal/provider/resource_host_record.go @@ -3,6 +3,7 @@ package provider import ( "context" "fmt" + "slices" "strconv" "strings" @@ -19,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/umich-vci/gobam" + "golang.org/x/exp/maps" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -323,12 +325,6 @@ func (r *HostRecordResource) Update(ctx context.Context, req resource.UpdateRequ return } - var addresses []string - resp.Diagnostics.Append(data.Addresses.ElementsAs(ctx, &addresses, false)...) - - var udfs map[string]string - resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, udfs, false)...) - if resp.Diagnostics.HasError() { resp.Diagnostics.Append(clientLogout(ctx, &client, mutex)...) resp.Diagnostics.Append(diag...) @@ -337,9 +333,10 @@ func (r *HostRecordResource) Update(ctx context.Context, req resource.UpdateRequ properties := "" - if !data.Addresses.Equal(state.Addresses) { - properties = properties + fmt.Sprintf("addresses=%s|", strings.Join(addresses, ",")) - } + // addresses must always be set + var addresses []string + resp.Diagnostics.Append(data.Addresses.ElementsAs(ctx, &addresses, false)...) + properties = properties + fmt.Sprintf("addresses=%s|", strings.Join(addresses, ",")) if !data.ReverseRecord.Equal(state.ReverseRecord) { properties = properties + fmt.Sprintf("reverseRecord=%s|", strconv.FormatBool(data.ReverseRecord.ValueBool())) @@ -349,10 +346,27 @@ func (r *HostRecordResource) Update(ctx context.Context, req resource.UpdateRequ properties = properties + fmt.Sprintf("ttl=%d|", data.TTL.ValueInt64()) } - for k, v := range udfs { - properties = properties + fmt.Sprintf("%s=%s|", k, v) + if !data.UserDefinedFields.Equal(state.UserDefinedFields) { + var udfs, oldudfs map[string]string + resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, &udfs, false)...) + resp.Diagnostics.Append(state.UserDefinedFields.ElementsAs(ctx, &oldudfs, false)...) + + for k, v := range udfs { + properties = properties + fmt.Sprintf("%s=%s|", k, v) + } + + // set keys that no longer exist to empty string + oldkeys := maps.Keys(oldudfs) + keys := maps.Keys(udfs) + for _, x := range oldkeys { + if !slices.Contains(keys, x) { + properties = properties + fmt.Sprintf("%s=|", x) + } + } } + tflog.Debug(ctx, fmt.Sprintf("Attempting to update HostRecord with properties: %s", properties)) + update := gobam.APIEntity{ Id: data.ID.ValueInt64Pointer(), Name: data.Name.ValueStringPointer(), diff --git a/internal/provider/resource_ip4_address.go b/internal/provider/resource_ip4_address.go index eeea351..232e670 100644 --- a/internal/provider/resource_ip4_address.go +++ b/internal/provider/resource_ip4_address.go @@ -3,19 +3,23 @@ package provider import ( "context" "fmt" + "slices" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/umich-vci/gobam" + "golang.org/x/exp/maps" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -177,6 +181,7 @@ func (r *IP4AddressResource) Schema(ctx context.Context, req resource.SchemaRequ MarkdownDescription: "A map of all user-definied fields associated with the IPv4 address.", Computed: true, Optional: true, + Default: mapdefault.StaticValue(basetypes.NewMapValueMust(types.StringType, nil)), ElementType: types.StringType, }, }, @@ -377,12 +382,22 @@ func (r *IP4AddressResource) Update(ctx context.Context, req resource.UpdateRequ } if !data.UserDefinedFields.Equal(state.UserDefinedFields) { - var udfs map[string]string + var udfs, oldudfs map[string]string resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, &udfs, false)...) + resp.Diagnostics.Append(state.UserDefinedFields.ElementsAs(ctx, &oldudfs, false)...) for k, v := range udfs { properties = properties + fmt.Sprintf("%s=%s|", k, v) } + + // set keys that no longer exist to empty string + oldkeys := maps.Keys(oldudfs) + keys := maps.Keys(udfs) + for _, x := range oldkeys { + if !slices.Contains(keys, x) { + properties = properties + fmt.Sprintf("%s=|", x) + } + } } update := gobam.APIEntity{ diff --git a/internal/provider/resource_ip4_network.go b/internal/provider/resource_ip4_network.go index a01dfc2..579cec1 100644 --- a/internal/provider/resource_ip4_network.go +++ b/internal/provider/resource_ip4_network.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "regexp" + "slices" "strconv" "strings" @@ -14,13 +15,16 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "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/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/umich-vci/gobam" + "golang.org/x/exp/maps" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -238,6 +242,8 @@ func (r *IP4NetworkResource) Schema(ctx context.Context, req resource.SchemaRequ "user_defined_fields": schema.MapAttribute{ MarkdownDescription: "A map of all user-definied fields associated with the IP4 Network.", Computed: true, + Optional: true, + Default: mapdefault.StaticValue(basetypes.NewMapValueMust(types.StringType, nil)), ElementType: types.StringType, }, }, @@ -364,6 +370,12 @@ func (r *IP4NetworkResource) Create(ctx context.Context, req resource.CreateRequ properties = properties + "locationCode=" + data.LocationCode.ValueString() + "|" } + var udfs map[string]string + data.UserDefinedFields.ElementsAs(ctx, &udfs, false) + for k, v := range udfs { + properties = properties + k + "=" + v + "|" + } + setName := gobam.APIEntity{ Id: data.ID.ValueInt64Pointer(), Name: data.Name.ValueStringPointer(), @@ -583,6 +595,25 @@ func (r *IP4NetworkResource) Update(ctx context.Context, req resource.UpdateRequ properties = properties + fmt.Sprintf("locationCode=%s|", data.LocationCode.ValueString()) } + if !data.UserDefinedFields.Equal(state.UserDefinedFields) { + var udfs, oldudfs map[string]string + resp.Diagnostics.Append(data.UserDefinedFields.ElementsAs(ctx, &udfs, false)...) + resp.Diagnostics.Append(state.UserDefinedFields.ElementsAs(ctx, &oldudfs, false)...) + + for k, v := range udfs { + properties = properties + fmt.Sprintf("%s=%s|", k, v) + } + + // set keys that no longer exist to empty string + oldkeys := maps.Keys(oldudfs) + keys := maps.Keys(udfs) + for _, x := range oldkeys { + if !slices.Contains(keys, x) { + properties = properties + fmt.Sprintf("%s=|", x) + } + } + } + update := gobam.APIEntity{ Id: state.ID.ValueInt64Pointer(), Name: data.Name.ValueStringPointer(), From d29790c14a500bc9cd7513754bd63afa111ef9ac Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 10:17:49 -0400 Subject: [PATCH 13/15] go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7e1315e..101eb8e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 github.com/umich-vci/gobam v0.0.0-20230705194030-32758b9f0f3c + golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 ) require ( @@ -69,7 +70,6 @@ require ( github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.14.3 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.18.0 // indirect From 4dc9d6508d24364af19b9a8350c53354dcc6abc9 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 10:19:11 -0400 Subject: [PATCH 14/15] go generate --- docs/data-sources/host_record.md | 2 +- docs/resources/host_record.md | 6 +++--- docs/resources/ip4_address.md | 17 +++++++++++++---- docs/resources/ip4_network.md | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/data-sources/host_record.md b/docs/data-sources/host_record.md index 297c249..fb51224 100644 --- a/docs/data-sources/host_record.md +++ b/docs/data-sources/host_record.md @@ -33,7 +33,6 @@ output "bluecat_host_addresses" { - `address_ids` (Set of Number) A set of all address ids associated with the host record. - `addresses` (Set of String) A set of all addresses associated with the host record. -- `custom_properties` (Map of String) A map of all custom properties associated with the host record. - `id` (Number) Entity identifier - `name` (String) The short name of the host record. - `parent_id` (Number) The ID of the parent of the host record. @@ -42,3 +41,4 @@ output "bluecat_host_addresses" { - `reverse_record` (Boolean) A boolean that represents if the host record should set reverse records. - `ttl` (Number) The TTL of the host record. - `type` (String) The type of the resource. +- `user_defined_fields` (Map of String) A map of all custom properties associated with the host record. diff --git a/docs/resources/host_record.md b/docs/resources/host_record.md index c4e6813..29cf12d 100644 --- a/docs/resources/host_record.md +++ b/docs/resources/host_record.md @@ -37,14 +37,14 @@ output "bluecat_hostname_fqdn" { ### Optional -- `comments` (String) Comments to be associated with the host record. -- `custom_properties` (Map of String) A map of all custom properties associated with the host record. - `reverse_record` (Boolean) If a reverse record should be created for addresses. - `ttl` (Number) The TTL for the host record. When set to -1, ignores the TTL. +- `user_defined_fields` (Map of String) A map of all user-definied fields associated with the Host Record. ### Read-Only - `absolute_name` (String) The absolute name (fqdn) of the host record. -- `id` (String) Host Record identifier +- `address_ids` (Set of Number) A set of all address ids associated with the host record. +- `id` (Number) Host Record identifier - `properties` (String) The properties of the host record as returned by the API (pipe delimited). - `type` (String) The type of the resource. diff --git a/docs/resources/ip4_address.md b/docs/resources/ip4_address.md index 3a30ff8..c8b390d 100644 --- a/docs/resources/ip4_address.md +++ b/docs/resources/ip4_address.md @@ -30,19 +30,28 @@ output "allocated_address" { ### Required - `configuration_id` (Number) The object ID of the Configuration that will hold the new address. If changed, forces a new resource. -- `name` (String) The name assigned to the IPv4 address. This is not related to DNS. - `parent_id` (Number) The object ID of the Configuration, Block, or Network to find the next available IPv4 address in. If changed, forces a new resource. ### Optional - `action` (String) The action to take on the next available IPv4 address. Must be one of: "MAKE_STATIC", "MAKE_RESERVED", or "MAKE_DHCP_RESERVED". If changed, forces a new resource. -- `custom_properties` (Map of String) A map of all custom properties associated with the IPv4 address. +- `location_code` (String) The location code of the address. - `mac_address` (String) The MAC address to associate with the IPv4 address. +- `name` (String) The display name of the IPv4 address. +- `user_defined_fields` (Map of String) A map of all user-definied fields associated with the IPv4 address. ### Read-Only - `address` (String) The IPv4 address that was allocated. -- `id` (Number) IP4 address identifier -- `properties` (String) The properties of the IPv4 address as returned by the API (pipe delimited). +- `expiry_time` (String) Time that IPv4 address lease expires. +- `id` (Number) IPv4 Address identifier. +- `lease_time` (String) Time that IPv4 address was leased. +- `location_inherited` (Boolean) The location is inherited. +- `parameter_request_list` (String) Time that IPv4 address lease expires. +- `properties` (String) The properties of the resource as returned by the API (pipe delimited). +- `router_port_info` (String) Connected router port information of the IPv4 address. - `state` (String) The state of the IPv4 address. +- `switch_port_info` (String) Connected switch port information of the IPv4 address. - `type` (String) The type of the resource. +- `vendor_class_identifier` (String) Time that IPv4 address lease expires. +- `vlan_info` (String) VLAN information of the IPv4 address. diff --git a/docs/resources/ip4_network.md b/docs/resources/ip4_network.md index da0d6aa..c172c57 100644 --- a/docs/resources/ip4_network.md +++ b/docs/resources/ip4_network.md @@ -49,6 +49,7 @@ output "bluecat_ip4_network_cidr" { - `name` (String) The display name of the IPv4 network. - `ping_before_assign` (Boolean) The network pings an address before assignment. - `traversal_method` (String) The traversal method used to find the range to allocate the network. Must be one of "NO_TRAVERSAL", "DEPTH_FIRST", or "BREADTH_FIRST". +- `user_defined_fields` (Map of String) A map of all user-definied fields associated with the IP4 Network. ### Read-Only @@ -59,4 +60,3 @@ output "bluecat_ip4_network_cidr" { - `shared_network` (String) The name of the shared network tag associated with the IP4 Network. - `template` (Number) The ID of the linked template - `type` (String) The type of the resource. -- `user_defined_fields` (Map of String) A map of all user-definied fields associated with the IP4 Network. From bd11ce569794a5ba15fea54c394e20936765f517 Mon Sep 17 00:00:00 2001 From: Adam Robinson Date: Tue, 19 Mar 2024 10:25:01 -0400 Subject: [PATCH 15/15] release 0.4.1 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9febe..4616b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.4.1 (March 19, 2024) + +BREAKING CHANGES: +* The schema on several resources has been changed, but now fields exposed + track what is documented for the Legacy API. +* There were breaking changes in 0.4.0 as well, but they were not documented or as consistent. + +IMPROVEMENTS: +* All fields on network, address, and host_record resource that are configurable + via the API should work now. +* Updated dependencies + ## 0.4.0 (February 29, 2024) IMPROVEMENTS: