From 1b3471fb7395f93fd5785a892ec6ca27027c7558 Mon Sep 17 00:00:00 2001 From: Anil Gadiyar HJ <89566379+AnilGadiyarHJ@users.noreply.github.com> Date: Fri, 3 May 2024 21:12:34 +0530 Subject: [PATCH] Anycast host resource implementation (#95) * datasource and resource for anycast * fixed PR checks * Ran go mod commands * Regenerated go.sum * added onprem host resource * basic UT working * basic UT working * fixed UTs * resolved new naming conventions from master and added examples * renamed resource files * fixed linter issue check * addressed PR comments * fixed linter issue * fixed host dependency in UT's --------- Co-authored-by: Ashish Mathew --- docs/data-sources/anycast_configs.md | 10 +- docs/resources/anycast_config.md | 9 +- docs/resources/anycast_host.md | 217 ++++++++++ .../bloxone_anycast_host/resource.tf | 52 +++ go.mod | 2 + internal/acctest/acctest.go | 2 +- internal/provider/provider.go | 9 +- ...e.go => api_anycast_config_data_source.go} | 34 +- ...=> api_anycast_config_data_source_test.go} | 39 +- ...urce.go => api_anycast_config_resource.go} | 0 ...go => api_anycast_config_resource_test.go} | 88 ++-- .../anycast/api_anycast_host_resource.go | 180 ++++++++ .../anycast/api_anycast_host_resource_test.go | 388 ++++++++++++++++++ .../anycast/model_proto_anycast_config_ref.go | 3 +- .../model_proto_anycast_config_response.go | 71 ---- .../anycast/model_proto_anycast_version.go | 79 ---- .../service/anycast/model_proto_bgp_config.go | 45 +- .../anycast/model_proto_bgp_neighbor.go | 37 +- .../anycast/model_proto_onprem_host.go | 75 ++-- .../anycast/model_proto_onprem_host_ref.go | 12 +- .../anycast/model_proto_ospf_config.go | 27 +- .../anycast/model_proto_ospfv3_config.go | 15 +- 22 files changed, 1081 insertions(+), 313 deletions(-) create mode 100644 docs/resources/anycast_host.md create mode 100644 examples/resources/bloxone_anycast_host/resource.tf rename internal/service/anycast/{api_on_prem_anycast_manager_data_source.go => api_anycast_config_data_source.go} (70%) rename internal/service/anycast/{api_on_prem_anycast_manager_data_source_test.go => api_anycast_config_data_source_test.go} (72%) rename internal/service/anycast/{api_on_prem_anycast_manager_resource.go => api_anycast_config_resource.go} (100%) rename internal/service/anycast/{api_on_prem_anycast_manager_resource_test.go => api_anycast_config_resource_test.go} (68%) create mode 100644 internal/service/anycast/api_anycast_host_resource.go create mode 100644 internal/service/anycast/api_anycast_host_resource_test.go delete mode 100644 internal/service/anycast/model_proto_anycast_config_response.go delete mode 100644 internal/service/anycast/model_proto_anycast_version.go diff --git a/docs/data-sources/anycast_configs.md b/docs/data-sources/anycast_configs.md index 2480db9c..90cd6f56 100644 --- a/docs/data-sources/anycast_configs.md +++ b/docs/data-sources/anycast_configs.md @@ -35,6 +35,7 @@ data "bloxone_anycast_configs" "example_all" { ### Optional +- `filters` (Map of String) Filter are used to return a more specific list of results. Filters can be used to match resources by specific attributes, e.g. name. If you specify multiple filters, the results returned will have only resources that match all the specified filters. - `host_id` (Number) Filter by host ID. - `is_configured` (Boolean) Filter by configuration status. - `service` (String) Filter by service type. @@ -72,14 +73,17 @@ Read-Only: ### Nested Schema for `results.onprem_hosts` -Optional: +Required: - `id` (Number) The resource identifier. + +Optional: + - `ip_address` (String) IPv4 address of the host in string format - `ipv6_address` (String) IPv6 address of the host in string format -- `name` (String) The name of the anycast. Read-Only: +- `name` (String) The name of the anycast. - `ophid` (String) Unique 32-character string identifier assigned to the host -- `runtime_status` (String) +- `runtime_status` (String) The runtime status of the host diff --git a/docs/resources/anycast_config.md b/docs/resources/anycast_config.md index 33189b20..08fbd4e7 100644 --- a/docs/resources/anycast_config.md +++ b/docs/resources/anycast_config.md @@ -62,14 +62,17 @@ resource "bloxone_anycast_config" "example" { ### Nested Schema for `onprem_hosts` -Optional: +Required: - `id` (Number) The resource identifier. + +Optional: + - `ip_address` (String) IPv4 address of the host in string format - `ipv6_address` (String) IPv6 address of the host in string format -- `name` (String) The name of the anycast. Read-Only: +- `name` (String) The name of the anycast. - `ophid` (String) Unique 32-character string identifier assigned to the host -- `runtime_status` (String) +- `runtime_status` (String) The runtime status of the host diff --git a/docs/resources/anycast_host.md b/docs/resources/anycast_host.md new file mode 100644 index 00000000..38cb214c --- /dev/null +++ b/docs/resources/anycast_host.md @@ -0,0 +1,217 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "bloxone_anycast_host Resource - terraform-provider-bloxone" +subcategory: "Anycast" +description: |- + Retrieve an Anycast host Configurations. +--- + +# bloxone_anycast_host (Resource) + +Retrieve an Anycast host Configurations. + +## Example Usage + +```terraform +data "bloxone_infra_hosts" "anycast_hosts" { + filters = { + display_name = "my-host" + } +} + +# Create an anycast config profile with onprem hosts +resource "bloxone_anycast_config" "example" { + anycast_ip_address = "10.10.10.1" + name = "Anycast_config_example" + service = "DNS" + +} + +# Adding an anycast host with BGP routing protocol +resource "bloxone_anycast_host" "example" { + id = one(data.bloxone_infra_hosts.anycast_hosts.results).legacy_id + + # Adding the anycast config profile and enabling BGP routing protocol + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + routing_protocols = ["BGP", "OSPF"] + } + ] + + # Adding the BGP configuration + config_bgp = { + asn = "6500" + asn_text = "6500" + holddown_secs = 180 + neighbors = [ + { + asn = "6501" + ip_address = "10.20.0.3" + } + ] + } + + # Adding the OSPF configuration + config_ospf = { + area_type = "STANDARD" + area = "10.0.0.1" + authentication_type = "Clear" + interface = "eth0" + authentication_key = "YXV0aGV" + hello_interval = 10 + dead_interval = 40 + retransmit_interval = 5 + transmit_delay = 1 + } +} +``` + + +## Schema + +### Required + +- `id` (Number) Numeric host identifier. + +### Optional + +- `anycast_config_refs` (Attributes List) Array of AnycastConfigRef structures, identifying the anycast configurations that this host is a member of. (see [below for nested schema](#nestedatt--anycast_config_refs)) +- `config_bgp` (Attributes) Struct BGP configuration; defines BGP configuration for one anycast-enabled on-prem host. (see [below for nested schema](#nestedatt--config_bgp)) +- `config_ospf` (Attributes) Struct OSPF configuration; defines OSPF configuration for one anycast-enabled on-prem host. (see [below for nested schema](#nestedatt--config_ospf)) +- `config_ospfv3` (Attributes) Struct OSPFv3 configuration; defines OSPFv3 configuration for one anycast-enabled on-prem host. (see [below for nested schema](#nestedatt--config_ospfv3)) + +### Read-Only + +- `created_at` (String) Date/time this host was created in anycast service database. +- `ip_address` (String) IPv4 address of the on-prem host +- `ipv6_address` (String) IPv6 address of the on-prem host +- `name` (String) User-friendly name of the host @example "dns-host-1", "Central Office Server". +- `updated_at` (String) Date/time this host was last updated in anycast service database. + + +### Nested Schema for `anycast_config_refs` + +Required: + +- `anycast_config_name` (String) + +Optional: + +- `routing_protocols` (List of String) Routing protocols enabled for this anycast configuration, on a particular host. Valid protocol names are "BGP", "OSPF"/"OSPFv2", "OSPFv3". + + + +### Nested Schema for `config_bgp` + +Required: + +- `asn` (Number) Autonomous system number of this BGP/anycast enabled on-prem host. +- `holddown_secs` (Number) BGP route hold-down timer. + +Optional: + +- `fields` (Attributes) Represents a set of symbolic field paths. (see [below for nested schema](#nestedatt--config_bgp--fields)) +- `keep_alive_secs` (Number) BGP keep-alive timer. +- `link_detect` (Boolean) Enable/disable link detection. +- `neighbors` (Attributes List) List of BgpNeighbor structs. (see [below for nested schema](#nestedatt--config_bgp--neighbors)) +- `preamble` (String) Any predefined BGP configuration, with embedded new lines; the preamble will be prepended to the generated BGP configuration. + +Read-Only: + +- `asn_text` (String) Autonomous system as text (supported in ASDOT or ASPLAIN format) Optional, requires the asn field to be set to the equivalent integer value of the ASDOT/ASPLAIN string contained in this field or be unset/zero. + Example: + + | ASDOT | ASPLAIN | INTEGER | VALID/INVALID | + |-------------|-------------|-------------|---------------| + | 0.1 | 1 | 1 | Valid | + | 1 | 1 | 1 | Valid | + | 65535 | 65535 | 65535 | Valid | + | 0.65535 | 65535 | 65535 | Valid | + | 1.0 | 65536 | 65536 | Valid | + | 1.1 | 65537 | 65537 | Valid | + | 1.65535 | 131071 | 131071 | Valid | + | 65535.0 | 4294901760 | 4294901760 | Valid | + | 65535.1 | 4294901761 | 4294901761 | Valid | + | 65535.65535 | 4294967295 | 4294967295 | Valid | + | 0.65536 | | | Invalid | + | 65535.655536| | | Invalid | + | 65536.0 | | | Invalid | + | 65536.65535 | | | Invalid | + | | 4294967296 | | Invalid | + + +### Nested Schema for `config_bgp.fields` + +Optional: + +- `paths` (List of String) The set of field mask paths. + + + +### Nested Schema for `config_bgp.neighbors` + +Required: + +- `asn` (Number) Autonomous system number of this BGP/anycast enabled on-prem host. + +Optional: + +- `asn_text` (String) Autonomous system as text (supported in ASDOT or ASPLAIN format) Optional, requires the asn field to be set to the equivalent integer value of the ASDOT/ASPLAIN string contained in this field or be unset/zero. + Example: + + | ASDOT | ASPLAIN | INTEGER | VALID/INVALID | + |-------------|-------------|-------------|---------------| + | 0.1 | 1 | 1 | Valid | + | 1 | 1 | 1 | Valid | + | 65535 | 65535 | 65535 | Valid | + | 0.65535 | 65535 | 65535 | Valid | + | 1.0 | 65536 | 65536 | Valid | + | 1.1 | 65537 | 65537 | Valid | + | 1.65535 | 131071 | 131071 | Valid | + | 65535.0 | 4294901760 | 4294901760 | Valid | + | 65535.1 | 4294901761 | 4294901761 | Valid | + | 65535.65535 | 4294967295 | 4294967295 | Valid | + | 0.65536 | | | Invalid | + | 65535.655536| | | Invalid | + | 65536.0 | | | Invalid | + | 65536.65535 | | | Invalid | + | | 4294967296 | | Invalid | +- `ip_address` (String) IPv4 address of the BGP neighbor +- `max_hop_count` (Number) Max hop count, if BGP multihop is enabled. +- `multihop` (Boolean) BGP multihop enabled or not. +- `password` (String) BGP protocol access password for this BGP neighbor, max 25 characters long. + + + + +### Nested Schema for `config_ospf` + +Optional: + +- `area` (String) OSPF area identifier; usually in the format of an IPv4 address (although not an address itself) +- `area_type` (String) OSPF area type; one of: "STANDARD", "STUB", "NSSA". +- `authentication_key` (String) OSPF authentication key. +- `authentication_key_id` (Number) title: Numeric OSPF authentication key identifier. +- `authentication_type` (String) OSPF authentication type; one of "Clear", "MD5". +- `cost` (Number) Explicit link cost for the interface. +- `dead_interval` (Number) OSPF router dead interval timer in seconds; must be the same for all the routers on the same network; default: 40 secs. +- `hello_interval` (Number) Period (in seconds) of OSPF Hello packet, sent by the OSPF router; must be the same for all the routers on the same network; default: 10 secs. +- `interface` (String) Name of the interface that is configured with external IP address of the host +- `preamble` (String) Any predefined OSPF configuration, with embedded new lines; the preamble will be prepended to the generated BGP configuration. +- `retransmit_interval` (Number) Period (in seconds) of retransmitting for OSPF Database Description and Link State Requests; default: 5 seconds. +- `transmit_delay` (Number) Estimated time to transmit link state advertisements; default: 1 sec. + + + +### Nested Schema for `config_ospfv3` + +Optional: + +- `area` (String) OSPF area identifier; usually in the format of an IPv4 address (although not an address itself) +- `cost` (Number) Explicit link cost for the interface. +- `dead_interval` (Number) OSPF router dead interval timer in seconds; must be the same for all the routers on the same network; default: 40 sec. +- `hello_interval` (Number) Period (in seconds) of OSPF Hello packet, sent by the OSPF router; must be the same for all the routers on the same network; default: 10 secs. +- `interface` (String) Name of the interface that is configured with external IP address of the host +- `retransmit_interval` (Number) Period (in seconds) of retransmitting for OSPF Database Description and Link State Requests; default: 5 seconds. +- `transmit_delay` (Number) Estimated time to transmit link state advertisements; default: 1 sec. diff --git a/examples/resources/bloxone_anycast_host/resource.tf b/examples/resources/bloxone_anycast_host/resource.tf new file mode 100644 index 00000000..6f9d2d0e --- /dev/null +++ b/examples/resources/bloxone_anycast_host/resource.tf @@ -0,0 +1,52 @@ +data "bloxone_infra_hosts" "anycast_hosts" { + filters = { + display_name = "my-host" + } +} + +# Create an anycast config profile with onprem hosts +resource "bloxone_anycast_config" "example" { + anycast_ip_address = "10.10.10.1" + name = "Anycast_config_example" + service = "DNS" + +} + +# Adding an anycast host with BGP routing protocol +resource "bloxone_anycast_host" "example" { + id = one(data.bloxone_infra_hosts.anycast_hosts.results).legacy_id + + # Adding the anycast config profile and enabling BGP routing protocol + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + routing_protocols = ["BGP", "OSPF"] + } + ] + + # Adding the BGP configuration + config_bgp = { + asn = "6500" + asn_text = "6500" + holddown_secs = 180 + neighbors = [ + { + asn = "6501" + ip_address = "10.20.0.3" + } + ] + } + + # Adding the OSPF configuration + config_ospf = { + area_type = "STANDARD" + area = "10.0.0.1" + authentication_type = "Clear" + interface = "eth0" + authentication_key = "YXV0aGV" + hello_interval = 10 + dead_interval = 40 + retransmit_interval = 5 + transmit_delay = 1 + } +} diff --git a/go.mod b/go.mod index 6afe08bf..5a2190e0 100644 --- a/go.mod +++ b/go.mod @@ -76,3 +76,5 @@ require ( google.golang.org/grpc v1.57.1 // indirect google.golang.org/protobuf v1.33.0 // indirect ) + +//replace github.com/infobloxopen/bloxone-go-client => ../bloxone-go-client diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 14740999..d9b4293d 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -41,7 +41,7 @@ func RandomNameWithPrefix(prefix string) string { } func RandomIP() string { - return fmt.Sprintf("%d.%d.%d.%d", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)) + return fmt.Sprintf("%d.%d.%d.%d", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255)) } func RandomName() string { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5e7be433..772040f0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,9 +12,8 @@ import ( bloxoneclient "github.com/infobloxopen/bloxone-go-client/client" "github.com/infobloxopen/bloxone-go-client/option" - "github.com/infobloxopen/terraform-provider-bloxone/internal/service/dfp" - "github.com/infobloxopen/terraform-provider-bloxone/internal/service/anycast" + "github.com/infobloxopen/terraform-provider-bloxone/internal/service/dfp" "github.com/infobloxopen/terraform-provider-bloxone/internal/service/dns_config" "github.com/infobloxopen/terraform-provider-bloxone/internal/service/dns_data" "github.com/infobloxopen/terraform-provider-bloxone/internal/service/fw" @@ -76,6 +75,7 @@ func (p *BloxOneProvider) Configure(ctx context.Context, req provider.ConfigureR option.WithClientName(fmt.Sprintf("terraform/%s#%s", p.version, p.commit)), option.WithAPIKey(data.APIKey.ValueString()), option.WithCSPUrl(data.CSPUrl.ValueString()), + option.WithDebug(false), ) resp.DataSourceData = client @@ -128,10 +128,11 @@ func (p *BloxOneProvider) Resources(_ context.Context) []func() resource.Resourc keys.NewTsigResource, - dfp.NewDfpResource, - + anycast.NewAnycastHostResource, anycast.NewAnycastConfigResource, + dfp.NewDfpResource, + fw.NewSecurityPolicyResource, fw.NewAccessCodeResource, fw.NewNamedListResource, diff --git a/internal/service/anycast/api_on_prem_anycast_manager_data_source.go b/internal/service/anycast/api_anycast_config_data_source.go similarity index 70% rename from internal/service/anycast/api_on_prem_anycast_manager_data_source.go rename to internal/service/anycast/api_anycast_config_data_source.go index 8b7dbbe4..d5d2cebf 100644 --- a/internal/service/anycast/api_on_prem_anycast_manager_data_source.go +++ b/internal/service/anycast/api_anycast_config_data_source.go @@ -16,27 +16,28 @@ import ( ) // Ensure provider defined types fully satisfy framework interfaces. -var _ datasource.DataSource = &OnPremAnycastManagerDataSource{} +var _ datasource.DataSource = &AnycastConfigDataSource{} func NewAnycastConfigDataSource() datasource.DataSource { - return &OnPremAnycastManagerDataSource{} + return &AnycastConfigDataSource{} } -// OnPremAnycastManagerDataSource defines the data source implementation. -type OnPremAnycastManagerDataSource struct { +// AnycastConfigDataSource defines the data source implementation. +type AnycastConfigDataSource struct { client *bloxoneclient.APIClient } -func (d *OnPremAnycastManagerDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { +func (d *AnycastConfigDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_" + "anycast_configs" } type ProtoAnycastConfigModelWithFilter struct { - TagFilters types.Map `tfsdk:"tag_filters"` - Results types.List `tfsdk:"results"` - Service types.String `tfsdk:"service"` - HostID types.Int64 `tfsdk:"host_id"` - IsConfigued types.Bool `tfsdk:"is_configured"` + Filters types.Map `tfsdk:"filters"` //todo : remove this + TagFilters types.Map `tfsdk:"tag_filters"` + Results types.List `tfsdk:"results"` + Service types.String `tfsdk:"service"` + HostID types.Int64 `tfsdk:"host_id"` + IsConfigured types.Bool `tfsdk:"is_configured"` } func (m *ProtoAnycastConfigModelWithFilter) FlattenResults(ctx context.Context, from []anycast.AnycastConfig, diags *diag.Diagnostics) { @@ -46,10 +47,15 @@ func (m *ProtoAnycastConfigModelWithFilter) FlattenResults(ctx context.Context, m.Results = flex.FlattenFrameworkListNestedBlock(ctx, from, ProtoAnycastConfigAttrTypes, diags, FlattenProtoAnycastConfig) } -func (d *OnPremAnycastManagerDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *AnycastConfigDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "Retrieve all named anycast configurations for the account.", Attributes: map[string]schema.Attribute{ + "filters": schema.MapAttribute{ + Description: "Filter are used to return a more specific list of results. Filters can be used to match resources by specific attributes, e.g. name. If you specify multiple filters, the results returned will have only resources that match all the specified filters.", + ElementType: types.StringType, + Optional: true, + }, "tag_filters": schema.MapAttribute{ Description: "Tag Filters are used to return a more specific list of results filtered by tags. If you specify multiple filters, the results returned will have only resources that match all the specified filters.", ElementType: types.StringType, @@ -77,7 +83,7 @@ func (d *OnPremAnycastManagerDataSource) Schema(ctx context.Context, req datasou } } -func (d *OnPremAnycastManagerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { +func (d *AnycastConfigDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { return @@ -97,7 +103,7 @@ func (d *OnPremAnycastManagerDataSource) Configure(ctx context.Context, req data d.client = client } -func (d *OnPremAnycastManagerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { +func (d *AnycastConfigDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var data ProtoAnycastConfigModelWithFilter // Read Terraform prior state data into the model @@ -112,7 +118,7 @@ func (d *OnPremAnycastManagerDataSource) Read(ctx context.Context, req datasourc GetAnycastConfigList(ctx). Service(flex.ExpandString(data.Service)). HostId(flex.ExpandInt64(data.HostID)). - IsConfigured(flex.ExpandBool(data.IsConfigued)). + IsConfigured(flex.ExpandBool(data.IsConfigured)). Tfilter(flex.ExpandFrameworkMapFilterString(ctx, data.TagFilters, &resp.Diagnostics)). Execute() if err != nil { diff --git a/internal/service/anycast/api_on_prem_anycast_manager_data_source_test.go b/internal/service/anycast/api_anycast_config_data_source_test.go similarity index 72% rename from internal/service/anycast/api_on_prem_anycast_manager_data_source_test.go rename to internal/service/anycast/api_anycast_config_data_source_test.go index 99ba23e8..db76fa51 100644 --- a/internal/service/anycast/api_on_prem_anycast_manager_data_source_test.go +++ b/internal/service/anycast/api_anycast_config_data_source_test.go @@ -11,7 +11,7 @@ import ( "github.com/infobloxopen/terraform-provider-bloxone/internal/acctest" ) -func TestAccOnPremAnycastManagerDataSource_Services(t *testing.T) { +func TestAccAnycastConfigDataSource_Services(t *testing.T) { dataSourceName := "data.bloxone_anycast_configs.test" resourceName := "bloxone_anycast_config.test_onprem_hosts" var v anycast.AnycastConfig @@ -20,24 +20,23 @@ func TestAccOnPremAnycastManagerDataSource_Services(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, - CheckDestroy: testAccCheckOnPremAnycastManagerDestroy(context.Background(), &v), + CheckDestroy: testAccCheckAnycastConfigResourceDestroy(context.Background(), &v), Steps: []resource.TestStep{ { - Config: testAccOnPremAnycastManagerDataSourceConfigService("10.1.1.2", anycastName, "DNS"), + Config: testAccAnycastConfigDataSourceConfigService("10.1.1.2", anycastName, "DNS"), Check: resource.ComposeTestCheckFunc( append([]resource.TestCheckFunc{ resource.TestCheckResourceAttr(dataSourceName, "results.#", "1"), resource.TestCheckResourceAttr(dataSourceName, "results.0.name", anycastName), - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), - }, testAccCheckOnPremAnycastManagerResourceAttrPair(resourceName, dataSourceName)...)..., + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), + }, testAccCheckAnycastConfigResourceAttrPair(resourceName, dataSourceName)...)..., ), }, }, }) } -func TestAccOnPremAnycastManagerDataSource_IsConfigured(t *testing.T) { - t.Skip("Skipping until ospf and bgp is implemented ") +func TestAccAnycastConfigDataSource_IsConfigured(t *testing.T) { dataSourceName := "data.bloxone_anycast_configs.test" resourceName := "bloxone_anycast_config.test_onprem_hosts" var v anycast.AnycastConfig @@ -46,22 +45,22 @@ func TestAccOnPremAnycastManagerDataSource_IsConfigured(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, - CheckDestroy: testAccCheckOnPremAnycastManagerDestroy(context.Background(), &v), + CheckDestroy: testAccCheckAnycastConfigResourceDestroy(context.Background(), &v), Steps: []resource.TestStep{ { - Config: testAccOnPremAnycastManagerDataSourceConfigIsConfigured("10.1.1.2", anycastName, "DNS"), + Config: testAccAnycastConfigDataSourceConfigIsConfigured("10.1.1.2", anycastName, "DNS"), Check: resource.ComposeTestCheckFunc( append([]resource.TestCheckFunc{ resource.TestCheckResourceAttr(dataSourceName, "results.0.name", anycastName), - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), - }, testAccCheckOnPremAnycastManagerResourceAttrPair(resourceName, dataSourceName)...)..., + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), + }, testAccCheckAnycastConfigResourceAttrPair(resourceName, dataSourceName)...)..., ), }, }, }) } -func TestAccOnPremAnycastManagerDataSource_TagFilters(t *testing.T) { +func TestAccAnycastConfigDataSource_TagFilters(t *testing.T) { dataSourceName := "data.bloxone_anycast_configs.test" resourceName := "bloxone_anycast_config.test" var v anycast.AnycastConfig @@ -70,14 +69,14 @@ func TestAccOnPremAnycastManagerDataSource_TagFilters(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, - CheckDestroy: testAccCheckOnPremAnycastManagerDestroy(context.Background(), &v), + CheckDestroy: testAccCheckAnycastConfigResourceDestroy(context.Background(), &v), Steps: []resource.TestStep{ { - Config: testAccOnPremAnycastManagerDataSourceConfigTagFilters("10.1.1.2", anycastName, "DNS", "value1"), + Config: testAccAnycastConfigDataSourceConfigTagFilters("10.1.1.2", anycastName, "DNS", "value1"), Check: resource.ComposeTestCheckFunc( append([]resource.TestCheckFunc{ - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), - }, testAccCheckOnPremAnycastManagerResourceAttrPair(resourceName, dataSourceName)...)..., + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), + }, testAccCheckAnycastConfigResourceAttrPair(resourceName, dataSourceName)...)..., ), }, }, @@ -86,7 +85,7 @@ func TestAccOnPremAnycastManagerDataSource_TagFilters(t *testing.T) { // below all TestAcc functions -func testAccCheckOnPremAnycastManagerResourceAttrPair(resourceName, dataSourceName string) []resource.TestCheckFunc { +func testAccCheckAnycastConfigResourceAttrPair(resourceName, dataSourceName string) []resource.TestCheckFunc { return []resource.TestCheckFunc{ resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceName, "results.0.account_id"), resource.TestCheckResourceAttrPair(resourceName, "anycast_ip_address", dataSourceName, "results.0.anycast_ip_address"), @@ -105,7 +104,7 @@ func testAccCheckOnPremAnycastManagerResourceAttrPair(resourceName, dataSourceNa } } -func testAccOnPremAnycastManagerDataSourceConfigService(anycastIpAddress, name, service string) string { +func testAccAnycastConfigDataSourceConfigService(anycastIpAddress, name, service string) string { return fmt.Sprintf(` data "bloxone_infra_services" "anycast_services" { filters = { @@ -131,7 +130,7 @@ data "bloxone_anycast_configs" "test" { `, anycastIpAddress, name, service, service) } -func testAccOnPremAnycastManagerDataSourceConfigIsConfigured(anycastIpAddress, name, service string) string { +func testAccAnycastConfigDataSourceConfigIsConfigured(anycastIpAddress, name, service string) string { return fmt.Sprintf(` data "bloxone_infra_services" "anycast_services" { filters = { @@ -157,7 +156,7 @@ data "bloxone_anycast_configs" "test" { `, anycastIpAddress, name, service) } -func testAccOnPremAnycastManagerDataSourceConfigTagFilters(anycastIpAddress, name, service, tagValue string) string { +func testAccAnycastConfigDataSourceConfigTagFilters(anycastIpAddress, name, service, tagValue string) string { return fmt.Sprintf(` resource "bloxone_anycast_config" "test" { anycast_ip_address = %q diff --git a/internal/service/anycast/api_on_prem_anycast_manager_resource.go b/internal/service/anycast/api_anycast_config_resource.go similarity index 100% rename from internal/service/anycast/api_on_prem_anycast_manager_resource.go rename to internal/service/anycast/api_anycast_config_resource.go diff --git a/internal/service/anycast/api_on_prem_anycast_manager_resource_test.go b/internal/service/anycast/api_anycast_config_resource_test.go similarity index 68% rename from internal/service/anycast/api_on_prem_anycast_manager_resource_test.go rename to internal/service/anycast/api_anycast_config_resource_test.go index ce4156c1..1c752f3c 100644 --- a/internal/service/anycast/api_on_prem_anycast_manager_resource_test.go +++ b/internal/service/anycast/api_anycast_config_resource_test.go @@ -15,9 +15,10 @@ import ( "github.com/infobloxopen/terraform-provider-bloxone/internal/acctest" ) -func TestAccOnPremAnycastManagerResource_basic(t *testing.T) { +func TestAccAnycastConfigResource_basic(t *testing.T) { var resourceName = "bloxone_anycast_config.test" var v anycast.AnycastConfig + anycastName := acctest.RandomNameWithPrefix("anycast") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -25,9 +26,10 @@ func TestAccOnPremAnycastManagerResource_basic(t *testing.T) { Steps: []resource.TestStep{ // Create and Read { - Config: testAccOnPremAnycastManagerBasicConfig("anycast1", "DHCP", "10.0.0.7"), + Config: testAccAnycastConfigResourceBasicConfig(anycastName, "DHCP", "10.0.0.8"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), + // Test Read Only fields resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "created_at"), resource.TestCheckResourceAttrSet(resourceName, "updated_at"), @@ -39,20 +41,21 @@ func TestAccOnPremAnycastManagerResource_basic(t *testing.T) { }) } -func TestAccOnPremAnycastManagerResource_disappears(t *testing.T) { +func TestAccAnycastConfigResource_disappears(t *testing.T) { resourceName := "bloxone_anycast_config.test" var v anycast.AnycastConfig + anycastName := acctest.RandomNameWithPrefix("anycast") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, - CheckDestroy: testAccCheckOnPremAnycastManagerDestroy(context.Background(), &v), + CheckDestroy: testAccCheckAnycastConfigResourceDestroy(context.Background(), &v), Steps: []resource.TestStep{ { - Config: testAccOnPremAnycastManagerBasicConfig("anycast_test", "DHCP", "10.0.0.7"), + Config: testAccAnycastConfigResourceBasicConfig(anycastName, "DHCP", "10.0.0.7"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), - testAccCheckOnPremAnycastManagerDisappears(context.Background(), &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceDisappears(context.Background(), &v), ), ExpectNonEmptyPlan: true, }, @@ -60,7 +63,7 @@ func TestAccOnPremAnycastManagerResource_disappears(t *testing.T) { }) } -func TestAccOnPremAnycastManagerResource_AnycastIpAddress(t *testing.T) { +func TestAccAnycastConfigResource_AnycastIpAddress(t *testing.T) { var resourceName = "bloxone_anycast_config.test_anycast_ip_address" var v anycast.AnycastConfig anycastName := acctest.RandomNameWithPrefix("anycast") @@ -71,18 +74,18 @@ func TestAccOnPremAnycastManagerResource_AnycastIpAddress(t *testing.T) { Steps: []resource.TestStep{ // Create and Read { - Config: testAccOnPremAnycastManagerAnycastIpAddress("10.0.0.2", anycastName, "DHCP"), + Config: testAccAnycastConfigResourceAnycastIpAddress("10.0.0.2", anycastName, "DHCP"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", anycastName), resource.TestCheckResourceAttr(resourceName, "anycast_ip_address", "10.0.0.2"), ), }, // Update and Read { - Config: testAccOnPremAnycastManagerAnycastIpAddress("10.0.0.3", anycastName, "DHCP"), + Config: testAccAnycastConfigResourceAnycastIpAddress("10.0.0.3", anycastName, "DHCP"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", anycastName), resource.TestCheckResourceAttr(resourceName, "anycast_ip_address", "10.0.0.3"), ), @@ -92,7 +95,7 @@ func TestAccOnPremAnycastManagerResource_AnycastIpAddress(t *testing.T) { }) } -func TestAccOnPremAnycastManagerResource_Description(t *testing.T) { +func TestAccAnycastConfigResource_Description(t *testing.T) { var resourceName = "bloxone_anycast_config.test_description" var v anycast.AnycastConfig anycastName := acctest.RandomNameWithPrefix("anycast") @@ -103,17 +106,17 @@ func TestAccOnPremAnycastManagerResource_Description(t *testing.T) { Steps: []resource.TestStep{ // Create and Read { - Config: testAccOnPremAnycastManagerDescription("10.0.0.2", anycastName, "DNS", "Anycast comment"), + Config: testAccAnycastConfigResourceDescription("10.0.0.2", anycastName, "DNS", "Anycast comment"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "description", "Anycast comment"), ), }, // Update and Read { - Config: testAccOnPremAnycastManagerDescription("10.0.0.2", anycastName, "DNS", "Anycast comment updated"), + Config: testAccAnycastConfigResourceDescription("10.0.0.2", anycastName, "DNS", "Anycast comment updated"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "description", "Anycast comment updated"), ), }, @@ -122,7 +125,7 @@ func TestAccOnPremAnycastManagerResource_Description(t *testing.T) { }) } -func TestAccOnPremAnycastManagerResource_Name(t *testing.T) { +func TestAccAnycastConfigResource_Name(t *testing.T) { var resourceName = "bloxone_anycast_config.test_name" var v anycast.AnycastConfig anycastName1 := acctest.RandomNameWithPrefix("anycast") @@ -134,17 +137,17 @@ func TestAccOnPremAnycastManagerResource_Name(t *testing.T) { Steps: []resource.TestStep{ // Create and Read { - Config: testAccOnPremAnycastManagerName("10.0.0.1", anycastName1, "DNS"), + Config: testAccAnycastConfigResourceName("10.0.0.1", anycastName1, "DNS"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", anycastName1), ), }, // Update and Read { - Config: testAccOnPremAnycastManagerName("10.0.0.1", anycastName2, "DNS"), + Config: testAccAnycastConfigResourceName("10.0.0.1", anycastName2, "DNS"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", anycastName2), ), }, @@ -153,7 +156,7 @@ func TestAccOnPremAnycastManagerResource_Name(t *testing.T) { }) } -func TestAccOnPremAnycastManagerResource_Service(t *testing.T) { +func TestAccAnycastConfigResource_Service(t *testing.T) { var resourceName = "bloxone_anycast_config.test_service" var v anycast.AnycastConfig anycastName := acctest.RandomNameWithPrefix("anycast") @@ -165,17 +168,17 @@ func TestAccOnPremAnycastManagerResource_Service(t *testing.T) { Steps: []resource.TestStep{ // Create and Read { - Config: testAccOnPremAnycastManagerService(anycastIP, anycastName, "DHCP"), + Config: testAccAnycastConfigResourceService(anycastIP, anycastName, "DHCP"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "service", "DHCP"), ), }, // Update and Read { - Config: testAccOnPremAnycastManagerService(anycastIP, anycastName, "DNS"), + Config: testAccAnycastConfigResourceService(anycastIP, anycastName, "DNS"), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "service", "DNS"), ), }, @@ -184,10 +187,11 @@ func TestAccOnPremAnycastManagerResource_Service(t *testing.T) { }) } -func TestAccOnPremAnycastManagerResource_Tags(t *testing.T) { +func TestAccAnycastConfigResource_Tags(t *testing.T) { var resourceName = "bloxone_anycast_config.test_tags" var v anycast.AnycastConfig anycastName := acctest.RandomNameWithPrefix("anycast") + anycastIP := acctest.RandomIP() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -195,24 +199,24 @@ func TestAccOnPremAnycastManagerResource_Tags(t *testing.T) { Steps: []resource.TestStep{ // Create and Read { - Config: testAccOnPremAnycastManagerTags("10.0.0.1", anycastName, "DNS", map[string]string{ + Config: testAccAnycastConfigResourceTags(anycastIP, anycastName, "DNS", map[string]string{ "tag1": "value1", "tag2": "value2", }), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.tag1", "value1"), resource.TestCheckResourceAttr(resourceName, "tags.tag2", "value2"), ), }, // Update and Read { - Config: testAccOnPremAnycastManagerTags("10.0.0.1", anycastName, "DNS", map[string]string{ + Config: testAccAnycastConfigResourceTags(anycastIP, anycastName, "DNS", map[string]string{ "tag2": "value2changed", "tag3": "value3", }), Check: resource.ComposeTestCheckFunc( - testAccCheckOnPremAnycastManagerExists(context.Background(), resourceName, &v), + testAccCheckAnycastConfigResourceExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.tag2", "value2changed"), resource.TestCheckResourceAttr(resourceName, "tags.tag3", "value3"), ), @@ -222,7 +226,7 @@ func TestAccOnPremAnycastManagerResource_Tags(t *testing.T) { }) } -func testAccCheckOnPremAnycastManagerExists(ctx context.Context, resourceName string, v *anycast.AnycastConfig) resource.TestCheckFunc { +func testAccCheckAnycastConfigResourceExists(ctx context.Context, resourceName string, v *anycast.AnycastConfig) resource.TestCheckFunc { // Verify the resource exists in the cloud return func(state *terraform.State) error { rs, ok := state.RootModule().Resources[resourceName] @@ -248,7 +252,7 @@ func testAccCheckOnPremAnycastManagerExists(ctx context.Context, resourceName st } } -func testAccCheckOnPremAnycastManagerDestroy(ctx context.Context, v *anycast.AnycastConfig) resource.TestCheckFunc { +func testAccCheckAnycastConfigResourceDestroy(ctx context.Context, v *anycast.AnycastConfig) resource.TestCheckFunc { // Verify the resource was destroyed return func(state *terraform.State) error { _, httpRes, err := acctest.BloxOneClient.AnycastAPI. @@ -266,7 +270,7 @@ func testAccCheckOnPremAnycastManagerDestroy(ctx context.Context, v *anycast.Any } } -func testAccCheckOnPremAnycastManagerDisappears(ctx context.Context, v *anycast.AnycastConfig) resource.TestCheckFunc { +func testAccCheckAnycastConfigResourceDisappears(ctx context.Context, v *anycast.AnycastConfig) resource.TestCheckFunc { // Delete the resource externally to verify disappears test return func(state *terraform.State) error { _, _, err := acctest.BloxOneClient.AnycastAPI. @@ -280,7 +284,7 @@ func testAccCheckOnPremAnycastManagerDisappears(ctx context.Context, v *anycast. } } -func testAccOnPremAnycastManagerBasicConfig(name, service, anycastIpAddress string) string { +func testAccAnycastConfigResourceBasicConfig(name, service, anycastIpAddress string) string { return fmt.Sprintf(` resource "bloxone_anycast_config" "test" { name = %q @@ -290,7 +294,7 @@ resource "bloxone_anycast_config" "test" { `, name, service, anycastIpAddress) } -func testAccOnPremAnycastManagerAnycastIpAddress(anycastIpAddress, name, service string) string { +func testAccAnycastConfigResourceAnycastIpAddress(anycastIpAddress, name, service string) string { return fmt.Sprintf(` resource "bloxone_anycast_config" "test_anycast_ip_address" { anycast_ip_address = %q @@ -300,7 +304,7 @@ resource "bloxone_anycast_config" "test_anycast_ip_address" { `, anycastIpAddress, name, service) } -func testAccOnPremAnycastManagerDescription(anycastIpAddress, name, service, description string) string { +func testAccAnycastConfigResourceDescription(anycastIpAddress, name, service, description string) string { return fmt.Sprintf(` resource "bloxone_anycast_config" "test_description" { anycast_ip_address = %q @@ -311,7 +315,7 @@ resource "bloxone_anycast_config" "test_description" { `, anycastIpAddress, name, service, description) } -func testAccOnPremAnycastManagerName(anycastIpAddress, name, service string) string { +func testAccAnycastConfigResourceName(anycastIpAddress, name, service string) string { return fmt.Sprintf(` resource "bloxone_anycast_config" "test_name" { anycast_ip_address = %q @@ -321,7 +325,7 @@ resource "bloxone_anycast_config" "test_name" { `, anycastIpAddress, name, service) } -func testAccOnPremAnycastManagerService(anycastIpAddress, name, service string) string { +func testAccAnycastConfigResourceService(anycastIpAddress, name, service string) string { return fmt.Sprintf(` resource "bloxone_anycast_config" "test_service" { anycast_ip_address = %q @@ -331,7 +335,7 @@ resource "bloxone_anycast_config" "test_service" { `, anycastIpAddress, name, service) } -func testAccOnPremAnycastManagerTags(anycastIpAddress, name, service string, tags map[string]string) string { +func testAccAnycastConfigResourceTags(anycastIpAddress, name, service string, tags map[string]string) string { tagsStr := "{\n" for k, v := range tags { tagsStr += fmt.Sprintf(` diff --git a/internal/service/anycast/api_anycast_host_resource.go b/internal/service/anycast/api_anycast_host_resource.go new file mode 100644 index 00000000..513c1d19 --- /dev/null +++ b/internal/service/anycast/api_anycast_host_resource.go @@ -0,0 +1,180 @@ +package anycast + +import ( + "context" + "fmt" + "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/types" + bloxoneclient "github.com/infobloxopen/bloxone-go-client/client" + "net/http" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &AnycastHostResource{} +var _ resource.ResourceWithImportState = &AnycastHostResource{} + +func NewAnycastHostResource() resource.Resource { + return &AnycastHostResource{} +} + +// OnPremAnycastManagerResource defines the resource implementation. +type AnycastHostResource struct { + client *bloxoneclient.APIClient +} + +func (r *AnycastHostResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "anycast_host" +} + +func (r *AnycastHostResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Retrieve an Anycast host Configurations.", + Attributes: ProtoOnpremHostResourceSchemaAttributes, + } +} + +func (r *AnycastHostResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*bloxoneclient.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *bloxoneclient.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *AnycastHostResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data ProtoOnpremHostModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + hostRes, _, err := r.client.InfraManagementAPI. + HostsAPI. + List(ctx). + Filter(fmt.Sprintf("legacy_id == '%d'", data.Id.ValueInt64())).Execute() + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create OnPremAnycastManager, got error: %s", err)) + return + } + if len(hostRes.GetResults()) != 1 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create OnPremAnycastManager, got error: %s", "Host not found")) + return + } + data.Name = types.StringValue(hostRes.GetResults()[0].DisplayName) + data.IpAddress = types.StringPointerValue(hostRes.GetResults()[0].IpAddress) + + apiRes, _, err := r.client.AnycastAPI. + OnPremAnycastManagerAPI. + UpdateOnpremHost(ctx, data.Id.ValueInt64()). + Body(*data.Expand(ctx, &resp.Diagnostics)). + Execute() + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create OnPremAnycastManager, got error: %s", err)) + return + } + + res := apiRes.GetResults() + data.Flatten(ctx, &res, &resp.Diagnostics) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AnycastHostResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data ProtoOnpremHostModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + apiRes, httpRes, err := r.client.AnycastAPI. + OnPremAnycastManagerAPI. + GetOnpremHost(ctx, data.Id.ValueInt64()). + Execute() + if err != nil { + if httpRes != nil && httpRes.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read OnPremAnycastManager, got error: %s", err)) + return + } + + res := apiRes.GetResults() + data.Flatten(ctx, &res, &resp.Diagnostics) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AnycastHostResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data ProtoOnpremHostModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + apiRes, _, err := r.client.AnycastAPI. + OnPremAnycastManagerAPI. + UpdateOnpremHost(ctx, data.Id.ValueInt64()). + Body(*data.Expand(ctx, &resp.Diagnostics)). + Execute() + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update OnPremAnycastManager, got error: %s", err)) + return + } + + res := apiRes.GetResults() + data.Flatten(ctx, &res, &resp.Diagnostics) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AnycastHostResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data ProtoOnpremHostModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + //OnPremAnycastManagerDeleteAnycastConfig + _, httpRes, err := r.client.AnycastAPI. + OnPremAnycastManagerAPI. + DeleteOnpremHost(ctx, data.Id.ValueInt64()). + Execute() + if err != nil { + if httpRes != nil && httpRes.StatusCode == http.StatusNotFound { + return + } + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete OnPremAnycastManager, got error: %s", err)) + return + } +} + +func (r *AnycastHostResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/service/anycast/api_anycast_host_resource_test.go b/internal/service/anycast/api_anycast_host_resource_test.go new file mode 100644 index 00000000..d807c728 --- /dev/null +++ b/internal/service/anycast/api_anycast_host_resource_test.go @@ -0,0 +1,388 @@ +package anycast_test + +import ( + "context" + "errors" + "fmt" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "net/http" + "strconv" + "strings" + "testing" + + "github.com/infobloxopen/bloxone-go-client/anycast" + "github.com/infobloxopen/terraform-provider-bloxone/internal/acctest" +) + +func TestAccAnycastHostResource_basic(t *testing.T) { + var resourceName = "bloxone_anycast_host.test" + var v anycast.OnpremHost + var anycastConfigName = acctest.RandomNameWithPrefix("anycast") + var anycastHostname = acctest.RandomNameWithPrefix("anycast_host") + anycastIP := acctest.RandomIP() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: testAccAnycastHostBasicConfig(anycastHostname, anycastConfigName, anycastIP), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "created_at"), + resource.TestCheckResourceAttrSet(resourceName, "updated_at"), + resource.TestCheckResourceAttrSet(resourceName, "name"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccAnycastHostResource_disappears(t *testing.T) { + resourceName := "bloxone_anycast_host.test" + var v anycast.OnpremHost + var anycastConfigName = acctest.RandomNameWithPrefix("anycast") + var anycastIP = acctest.RandomIP() + var anycastHostname = acctest.RandomNameWithPrefix("anycast_host") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckAnycastHostDestroy(context.Background(), &v), + Steps: []resource.TestStep{ + { + Config: testAccAnycastHostBasicConfig(anycastHostname, anycastConfigName, anycastIP), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + testAccCheckAnycastHostDisappears(context.Background(), &v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAnycastHostResource_enableRouting(t *testing.T) { + var resourceName = "bloxone_anycast_host.test" + var v anycast.OnpremHost + var anycastConfigName = acctest.RandomNameWithPrefix("anycast") + var anycastIP = acctest.RandomIP() + var anycastHostname = acctest.RandomNameWithPrefix("anycast_host") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: testAccAnycastHostEnableRoutingBGP("BGP", anycastHostname, anycastConfigName, anycastIP), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "config_bgp.asn", "6500"), + resource.TestCheckResourceAttr(resourceName, "config_bgp.holddown_secs", "180"), + resource.TestCheckResourceAttr(resourceName, "config_bgp.neighbors.#", "1"), + resource.TestCheckResourceAttr(resourceName, "config_bgp.neighbors.0.asn", "6501"), + ), + }, + // Update and Read + { + Config: testAccAnycastHostEnableRoutingOSPF("OSPF", anycastHostname, anycastConfigName, anycastIP), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "config_ospf.area_type", "STANDARD"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.area", "10.10.0.1"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.authentication_type", "Clear"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.interface", "eth0"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.authentication_key", "YXV0aGV"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.hello_interval", "10"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccAnycastHostResource_BGP(t *testing.T) { + var resourceName = "bloxone_anycast_host.test" + var v anycast.OnpremHost + var anycastConfigName = acctest.RandomNameWithPrefix("anycast") + var anycastIP = acctest.RandomIP() + var anycastHostname = acctest.RandomNameWithPrefix("anycast_host") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: testAccAnycastHostBGP("BGP", anycastHostname, anycastConfigName, anycastIP, 6500, 180), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "config_bgp.asn", "6500"), + resource.TestCheckResourceAttr(resourceName, "config_bgp.holddown_secs", "180"), + ), + }, + // Update and Read + { + Config: testAccAnycastHostBGP("BGP", anycastHostname, anycastConfigName, anycastIP, 6601, 200), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "config_bgp.asn", "6601"), + resource.TestCheckResourceAttr(resourceName, "config_bgp.holddown_secs", "200"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccAnycastHostResource_OSPF(t *testing.T) { + var resourceName = "bloxone_anycast_host.test" + var v anycast.OnpremHost + var anycastConfigName = acctest.RandomNameWithPrefix("anycast") + var anycastIP = acctest.RandomIP() + var anycastHostname = acctest.RandomNameWithPrefix("anycast_host") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: testAccAnycastHostOSPF("OSPF", "STANDARD", "10.10.0.1", "Clear", "eth0", anycastHostname, anycastConfigName, anycastIP, 10, 40, 5, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "config_ospf.area_type", "STANDARD"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.area", "10.10.0.1"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.authentication_type", "Clear"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.interface", "eth0"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.authentication_key", "YXV0aGV"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.hello_interval", "10"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.dead_interval", "40"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.retransmit_interval", "5"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.transmit_delay", "1"), + ), + }, + // Update and Read + { + Config: testAccAnycastHostOSPF("OSPF", "NSSA", "10.10.0.2", "MD5", "ens160", anycastHostname, anycastConfigName, anycastIP, 20, 50, 10, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAnycastHostExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "config_ospf.area_type", "NSSA"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.area", "10.10.0.2"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.authentication_type", "MD5"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.interface", "ens160"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.authentication_key", "YXV0aGV"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.hello_interval", "20"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.dead_interval", "50"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.retransmit_interval", "10"), + resource.TestCheckResourceAttr(resourceName, "config_ospf.transmit_delay", "2"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccCheckAnycastHostExists(ctx context.Context, resourceName string, v *anycast.OnpremHost) resource.TestCheckFunc { + // Verify the resource exists in the cloud + return func(state *terraform.State) error { + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + id, err := strconv.ParseInt(rs.Primary.ID, 10, 64) + if err != nil { + return fmt.Errorf("error parsing ID: %v", err) + } + apiRes, _, err := acctest.BloxOneClient.AnycastAPI. + OnPremAnycastManagerAPI. + GetOnpremHost(ctx, id). + Execute() + if err != nil { + return err + } + if !apiRes.HasResults() { + return fmt.Errorf("expected result to be returned: %s", resourceName) + } + *v = apiRes.GetResults() + return nil + } +} + +func testAccCheckAnycastHostDestroy(ctx context.Context, v *anycast.OnpremHost) resource.TestCheckFunc { + // Verify the resource was destroyed + return func(state *terraform.State) error { + _, httpRes, err := acctest.BloxOneClient.AnycastAPI. + OnPremAnycastManagerAPI. + GetOnpremHost(ctx, *v.Id). + Execute() + if err != nil { + if httpRes != nil && httpRes.StatusCode == http.StatusNotFound { + // resource was deleted + return nil + } + return err + } + return errors.New("expected to be deleted") + } +} + +func testAccCheckAnycastHostDisappears(ctx context.Context, v *anycast.OnpremHost) resource.TestCheckFunc { + // Delete the resource externally to verify disappears test + return func(state *terraform.State) error { + _, _, err := acctest.BloxOneClient.AnycastAPI. + OnPremAnycastManagerAPI. + DeleteOnpremHost(ctx, *v.Id). //testAccCheckAnycastHostDisappears + Execute() + if err != nil { + return err + } + return nil + } +} + +func testAccBaseWithAnycastConfig(hostName, name, anycastIpAddress string) string { + return fmt.Sprintf(` +resource "bloxone_infra_host" "test_host" { + display_name = %q +} + +resource "bloxone_anycast_config" "test_onprem_hosts" { + name = "%s" + anycast_ip_address = "%s" + service = "DNS" + +} +`, hostName, name, anycastIpAddress) +} + +func testAccAnycastHostBasicConfig(hostName, anycastConfigName, anycastIP string) string { + config := ` +resource "bloxone_anycast_host" "test" { + id = bloxone_infra_host.test_host.legacy_id + + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + } + ] +} +` + return strings.Join([]string{testAccBaseWithAnycastConfig(hostName, anycastConfigName, anycastIP), config}, "") +} + +func testAccAnycastHostEnableRoutingBGP(routingProtocols, hostName, anycastConfigName, anycastIP string) string { + config := fmt.Sprintf(` +resource "bloxone_anycast_host" "test" { + id = bloxone_infra_host.test_host.legacy_id + + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + routing_protocols = ["%s"] + } + ] + + config_bgp = { + asn = "6500" + holddown_secs = 180 + neighbors = [ + { + asn = "6501" + ip_address = "172.28.4.198" + } + ] + } + } +`, routingProtocols) + return strings.Join([]string{testAccBaseWithAnycastConfig(hostName, anycastConfigName, anycastIP), config}, "") +} + +func testAccAnycastHostEnableRoutingOSPF(routing_protocols, hostName, anycastConfigName, anycastIP string) string { + config := fmt.Sprintf(` +resource "bloxone_anycast_host" "test" { + id = bloxone_infra_host.test_host.legacy_id + + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + routing_protocols = ["%s"] + } + ] + + config_ospf = { + area_type = "STANDARD" + area = "10.10.0.1" + authentication_type = "Clear" + interface = "eth0" + authentication_key = "YXV0aGV" + hello_interval = 10 + dead_interval = 40 + retransmit_interval = 5 + transmit_delay = 1 + } +} +`, routing_protocols) + return strings.Join([]string{testAccBaseWithAnycastConfig(hostName, anycastConfigName, anycastIP), config}, "") +} + +func testAccAnycastHostBGP(routingProtocols, hostName, anycastConfigName, anycastIP string, asn, holddown_secs int64) string { + config := fmt.Sprintf(` +resource "bloxone_anycast_host" "test" { + id = bloxone_infra_host.test_host.legacy_id + + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + routing_protocols = ["%s"] + } + ] + + config_bgp = { + asn = "%d" + holddown_secs = "%d" + neighbors = [ + { + asn = "6501" + ip_address = "172.28.4.198" + } + ] + } + } +`, routingProtocols, asn, holddown_secs) + return strings.Join([]string{testAccBaseWithAnycastConfig(hostName, anycastConfigName, anycastIP), config}, "") +} + +func testAccAnycastHostOSPF(routing_protocols, area_type, area, authentication_type, ospfInterface, hostName, anycastConfigName, anycastIP string, hello_interval, dead_interval, retransmit_interval, transmit_delay int64) string { + config := fmt.Sprintf(` +resource "bloxone_anycast_host" "test" { + id = bloxone_infra_host.test_host.legacy_id + + anycast_config_refs = [ + { + anycast_config_name = bloxone_anycast_config.test_onprem_hosts.name + routing_protocols = ["%s"] + } + ] + + config_ospf = { + area_type = "%s" + area = "%s" + authentication_type = "%s" + interface = "%s" + authentication_key = "YXV0aGV" + authentication_key_id = "1" + hello_interval = "%d" + dead_interval = "%d" + retransmit_interval ="%d" + transmit_delay = "%d" + } +} +`, routing_protocols, area_type, area, authentication_type, ospfInterface, hello_interval, dead_interval, retransmit_interval, transmit_delay) + return strings.Join([]string{testAccBaseWithAnycastConfig(hostName, anycastConfigName, anycastIP), config}, "") +} diff --git a/internal/service/anycast/model_proto_anycast_config_ref.go b/internal/service/anycast/model_proto_anycast_config_ref.go index f3e88678..f9bc1485 100644 --- a/internal/service/anycast/model_proto_anycast_config_ref.go +++ b/internal/service/anycast/model_proto_anycast_config_ref.go @@ -26,11 +26,12 @@ var ProtoAnycastConfigRefAttrTypes = map[string]attr.Type{ var ProtoAnycastConfigRefResourceSchemaAttributes = map[string]schema.Attribute{ "anycast_config_name": schema.StringAttribute{ - Optional: true, + Required: true, }, "routing_protocols": schema.ListAttribute{ ElementType: types.StringType, Optional: true, + Computed: true, MarkdownDescription: "Routing protocols enabled for this anycast configuration, on a particular host. Valid protocol names are \"BGP\", \"OSPF\"/\"OSPFv2\", \"OSPFv3\".", }, } diff --git a/internal/service/anycast/model_proto_anycast_config_response.go b/internal/service/anycast/model_proto_anycast_config_response.go deleted file mode 100644 index 4ee1952f..00000000 --- a/internal/service/anycast/model_proto_anycast_config_response.go +++ /dev/null @@ -1,71 +0,0 @@ -package anycast - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - schema "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - - "github.com/infobloxopen/bloxone-go-client/anycast" -) - -type ProtoAnycastConfigResponseModel struct { - Results types.Object `tfsdk:"results"` -} - -var ProtoAnycastConfigResponseAttrTypes = map[string]attr.Type{ - "results": types.ObjectType{AttrTypes: ProtoAnycastConfigAttrTypes}, -} - -var ProtoAnycastConfigResponseResourceSchemaAttributes = map[string]schema.Attribute{ - "results": schema.SingleNestedAttribute{ - Attributes: ProtoAnycastConfigResourceSchemaAttributes, - Optional: true, - }, -} - -func ExpandProtoAnycastConfigResponse(ctx context.Context, o types.Object, diags *diag.Diagnostics) *anycast.AnycastConfigResponse { - if o.IsNull() || o.IsUnknown() { - return nil - } - var m ProtoAnycastConfigResponseModel - diags.Append(o.As(ctx, &m, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return nil - } - return m.Expand(ctx, diags) -} - -func (m *ProtoAnycastConfigResponseModel) Expand(ctx context.Context, diags *diag.Diagnostics) *anycast.AnycastConfigResponse { - if m == nil { - return nil - } - to := &anycast.AnycastConfigResponse{ - Results: ExpandProtoAnycastConfig(ctx, m.Results, diags), - } - return to -} - -func FlattenProtoAnycastConfigResponse(ctx context.Context, from *anycast.AnycastConfigResponse, diags *diag.Diagnostics) types.Object { - if from == nil { - return types.ObjectNull(ProtoAnycastConfigResponseAttrTypes) - } - m := ProtoAnycastConfigResponseModel{} - m.Flatten(ctx, from, diags) - t, d := types.ObjectValueFrom(ctx, ProtoAnycastConfigResponseAttrTypes, m) - diags.Append(d...) - return t -} - -func (m *ProtoAnycastConfigResponseModel) Flatten(ctx context.Context, from *anycast.AnycastConfigResponse, diags *diag.Diagnostics) { - if from == nil { - return - } - if m == nil { - *m = ProtoAnycastConfigResponseModel{} - } - m.Results = FlattenProtoAnycastConfig(ctx, from.Results, diags) -} diff --git a/internal/service/anycast/model_proto_anycast_version.go b/internal/service/anycast/model_proto_anycast_version.go deleted file mode 100644 index acb6b27c..00000000 --- a/internal/service/anycast/model_proto_anycast_version.go +++ /dev/null @@ -1,79 +0,0 @@ -package anycast - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - schema "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - - "github.com/infobloxopen/bloxone-go-client/anycast" - - "github.com/infobloxopen/terraform-provider-bloxone/internal/flex" -) - -type ProtoAnycastVersionModel struct { - AccountId types.Int64 `tfsdk:"account_id"` - Version types.String `tfsdk:"version"` -} - -var ProtoAnycastVersionAttrTypes = map[string]attr.Type{ - "account_id": types.Int64Type, - "version": types.StringType, -} - -var ProtoAnycastVersionResourceSchemaAttributes = map[string]schema.Attribute{ - "account_id": schema.Int64Attribute{ - Optional: true, - }, - "version": schema.StringAttribute{ - Optional: true, - }, -} - -func ExpandProtoAnycastVersion(ctx context.Context, o types.Object, diags *diag.Diagnostics) *anycast.AnycastVersion { - if o.IsNull() || o.IsUnknown() { - return nil - } - var m ProtoAnycastVersionModel - diags.Append(o.As(ctx, &m, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return nil - } - return m.Expand(ctx, diags) -} - -func (m *ProtoAnycastVersionModel) Expand(ctx context.Context, diags *diag.Diagnostics) *anycast.AnycastVersion { - if m == nil { - return nil - } - to := &anycast.AnycastVersion{ - AccountId: flex.ExpandInt64Pointer(m.AccountId), - Version: flex.ExpandStringPointer(m.Version), - } - return to -} - -func FlattenProtoAnycastVersion(ctx context.Context, from *anycast.AnycastVersion, diags *diag.Diagnostics) types.Object { - if from == nil { - return types.ObjectNull(ProtoAnycastVersionAttrTypes) - } - m := ProtoAnycastVersionModel{} - m.Flatten(ctx, from, diags) - t, d := types.ObjectValueFrom(ctx, ProtoAnycastVersionAttrTypes, m) - diags.Append(d...) - return t -} - -func (m *ProtoAnycastVersionModel) Flatten(ctx context.Context, from *anycast.AnycastVersion, diags *diag.Diagnostics) { - if from == nil { - return - } - if m == nil { - *m = ProtoAnycastVersionModel{} - } - m.AccountId = flex.FlattenInt64Pointer(from.AccountId) - m.Version = flex.FlattenStringPointer(from.Version) -} diff --git a/internal/service/anycast/model_proto_bgp_config.go b/internal/service/anycast/model_proto_bgp_config.go index 929e3314..163c5711 100644 --- a/internal/service/anycast/model_proto_bgp_config.go +++ b/internal/service/anycast/model_proto_bgp_config.go @@ -38,30 +38,57 @@ var ProtoBgpConfigAttrTypes = map[string]attr.Type{ var ProtoBgpConfigResourceSchemaAttributes = map[string]schema.Attribute{ "asn": schema.Int64Attribute{ - Optional: true, + Required: true, + MarkdownDescription: `Autonomous system number of this BGP/anycast enabled on-prem host.`, }, "asn_text": schema.StringAttribute{ - Optional: true, - MarkdownDescription: "Examples: ASDOT ASPLAIN INTEGER VALID/INVALID 0.1 1 1 Valid 1 1 1 Valid 65535 65535 65535 Valid 0.65535 65535 65535 Valid 1.0 65536 65536 Valid 1.1 65537 65537 Valid 1.65535 131071 131071 Valid 65535.0 4294901760 4294901760 Valid 65535.1 4294901761 4294901761 Valid 65535.65535 4294967295 4294967295 Valid 0.65536 Invalid 65535.655536 Invalid 65536.0 Invalid 65536.65535 Invalid 4294967296 Invalid", + Computed: true, + MarkdownDescription: `Autonomous system as text (supported in ASDOT or ASPLAIN format) Optional, requires the asn field to be set to the equivalent integer value of the ASDOT/ASPLAIN string contained in this field or be unset/zero. + Example: + + | ASDOT | ASPLAIN | INTEGER | VALID/INVALID | + |-------------|-------------|-------------|---------------| + | 0.1 | 1 | 1 | Valid | + | 1 | 1 | 1 | Valid | + | 65535 | 65535 | 65535 | Valid | + | 0.65535 | 65535 | 65535 | Valid | + | 1.0 | 65536 | 65536 | Valid | + | 1.1 | 65537 | 65537 | Valid | + | 1.65535 | 131071 | 131071 | Valid | + | 65535.0 | 4294901760 | 4294901760 | Valid | + | 65535.1 | 4294901761 | 4294901761 | Valid | + | 65535.65535 | 4294967295 | 4294967295 | Valid | + | 0.65536 | | | Invalid | + | 65535.655536| | | Invalid | + | 65536.0 | | | Invalid | + | 65536.65535 | | | Invalid | + | | 4294967296 | | Invalid | +`, }, "fields": schema.SingleNestedAttribute{ - Attributes: ProtobufFieldMaskResourceSchemaAttributes, - Optional: true, + Attributes: ProtobufFieldMaskResourceSchemaAttributes, + Optional: true, + MarkdownDescription: `Represents a set of symbolic field paths.`, }, "holddown_secs": schema.Int64Attribute{ - Optional: true, + Required: true, + MarkdownDescription: `BGP route hold-down timer.`, }, "keep_alive_secs": schema.Int64Attribute{ - Optional: true, + Optional: true, + Computed: true, + MarkdownDescription: `BGP keep-alive timer.`, }, "link_detect": schema.BoolAttribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Enable/disable link detection.`, }, "neighbors": schema.ListNestedAttribute{ NestedObject: schema.NestedAttributeObject{ Attributes: ProtoBgpNeighborResourceSchemaAttributes, }, - Optional: true, + Optional: true, + MarkdownDescription: `List of BgpNeighbor structs.`, }, "preamble": schema.StringAttribute{ Optional: true, diff --git a/internal/service/anycast/model_proto_bgp_neighbor.go b/internal/service/anycast/model_proto_bgp_neighbor.go index 5805d07d..a8919d22 100644 --- a/internal/service/anycast/model_proto_bgp_neighbor.go +++ b/internal/service/anycast/model_proto_bgp_neighbor.go @@ -34,24 +34,49 @@ var ProtoBgpNeighborAttrTypes = map[string]attr.Type{ var ProtoBgpNeighborResourceSchemaAttributes = map[string]schema.Attribute{ "asn": schema.Int64Attribute{ - Optional: true, + Required: true, + MarkdownDescription: `Autonomous system number of this BGP/anycast enabled on-prem host.`, }, "asn_text": schema.StringAttribute{ - Optional: true, - MarkdownDescription: "Examples: ASDOT ASPLAIN INTEGER VALID/INVALID 0.1 1 1 Valid 1 1 1 Valid 65535 65535 65535 Valid 0.65535 65535 65535 Valid 1.0 65536 65536 Valid 1.1 65537 65537 Valid 1.65535 131071 131071 Valid 65535.0 4294901760 4294901760 Valid 65535.1 4294901761 4294901761 Valid 65535.65535 4294967295 4294967295 Valid 0.65536 Invalid 65535.655536 Invalid 65536.0 Invalid 65536.65535 Invalid 4294967296 Invalid", + Computed: true, + Optional: true, + MarkdownDescription: `Autonomous system as text (supported in ASDOT or ASPLAIN format) Optional, requires the asn field to be set to the equivalent integer value of the ASDOT/ASPLAIN string contained in this field or be unset/zero. + Example: + + | ASDOT | ASPLAIN | INTEGER | VALID/INVALID | + |-------------|-------------|-------------|---------------| + | 0.1 | 1 | 1 | Valid | + | 1 | 1 | 1 | Valid | + | 65535 | 65535 | 65535 | Valid | + | 0.65535 | 65535 | 65535 | Valid | + | 1.0 | 65536 | 65536 | Valid | + | 1.1 | 65537 | 65537 | Valid | + | 1.65535 | 131071 | 131071 | Valid | + | 65535.0 | 4294901760 | 4294901760 | Valid | + | 65535.1 | 4294901761 | 4294901761 | Valid | + | 65535.65535 | 4294967295 | 4294967295 | Valid | + | 0.65536 | | | Invalid | + | 65535.655536| | | Invalid | + | 65536.0 | | | Invalid | + | 65536.65535 | | | Invalid | + | | 4294967296 | | Invalid | +`, }, "ip_address": schema.StringAttribute{ Optional: true, MarkdownDescription: "IPv4 address of the BGP neighbor", }, "max_hop_count": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Max hop count, if BGP multihop is enabled.`, }, "multihop": schema.BoolAttribute{ - Optional: true, + Optional: true, + MarkdownDescription: `BGP multihop enabled or not.`, }, "password": schema.StringAttribute{ - Optional: true, + Optional: true, + MarkdownDescription: `BGP protocol access password for this BGP neighbor, max 25 characters long.`, }, } diff --git a/internal/service/anycast/model_proto_onprem_host.go b/internal/service/anycast/model_proto_onprem_host.go index ff7e4488..324548ac 100644 --- a/internal/service/anycast/model_proto_onprem_host.go +++ b/internal/service/anycast/model_proto_onprem_host.go @@ -7,9 +7,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" schema "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/planmodifier" + "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/infobloxopen/bloxone-go-client/anycast" "github.com/infobloxopen/terraform-provider-bloxone/internal/flex" @@ -46,56 +47,58 @@ var ProtoOnpremHostResourceSchemaAttributes = map[string]schema.Attribute{ NestedObject: schema.NestedAttributeObject{ Attributes: ProtoAnycastConfigRefResourceSchemaAttributes, }, - Optional: true, + Optional: true, + MarkdownDescription: `Array of AnycastConfigRef structures, identifying the anycast configurations that this host is a member of.`, }, "config_bgp": schema.SingleNestedAttribute{ - Attributes: ProtoBgpConfigResourceSchemaAttributes, - Optional: true, + Attributes: ProtoBgpConfigResourceSchemaAttributes, + Optional: true, + MarkdownDescription: `Struct BGP configuration; defines BGP configuration for one anycast-enabled on-prem host.`, }, "config_ospf": schema.SingleNestedAttribute{ - Attributes: ProtoOspfConfigResourceSchemaAttributes, - Optional: true, + Attributes: ProtoOspfConfigResourceSchemaAttributes, + Optional: true, + MarkdownDescription: `Struct OSPF configuration; defines OSPF configuration for one anycast-enabled on-prem host.`, }, "config_ospfv3": schema.SingleNestedAttribute{ - Attributes: ProtoOspfv3ConfigResourceSchemaAttributes, - Optional: true, + Attributes: ProtoOspfv3ConfigResourceSchemaAttributes, + Optional: true, + MarkdownDescription: `Struct OSPFv3 configuration; defines OSPFv3 configuration for one anycast-enabled on-prem host.`, }, "created_at": schema.StringAttribute{ - CustomType: timetypes.RFC3339Type{}, - Optional: true, + CustomType: timetypes.RFC3339Type{}, + Computed: true, + MarkdownDescription: `Date/time this host was created in anycast service database.`, }, "id": schema.Int64Attribute{ - Computed: true, + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + MarkdownDescription: `Numeric host identifier.`, }, "ip_address": schema.StringAttribute{ - Optional: true, + Computed: true, MarkdownDescription: "IPv4 address of the on-prem host", }, "ipv6_address": schema.StringAttribute{ - Optional: true, + Computed: true, MarkdownDescription: "IPv6 address of the on-prem host", }, "name": schema.StringAttribute{ - Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + MarkdownDescription: `User-friendly name of the host @example "dns-host-1", "Central Office Server".`, }, "updated_at": schema.StringAttribute{ - CustomType: timetypes.RFC3339Type{}, - Optional: true, + CustomType: timetypes.RFC3339Type{}, + Computed: true, + MarkdownDescription: `Date/time this host was last updated in anycast service database.`, }, } -func ExpandProtoOnpremHost(ctx context.Context, o types.Object, diags *diag.Diagnostics) *anycast.OnpremHost { - if o.IsNull() || o.IsUnknown() { - return nil - } - var m ProtoOnpremHostModel - diags.Append(o.As(ctx, &m, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return nil - } - return m.Expand(ctx, diags) -} - func (m *ProtoOnpremHostModel) Expand(ctx context.Context, diags *diag.Diagnostics) *anycast.OnpremHost { if m == nil { return nil @@ -105,26 +108,14 @@ func (m *ProtoOnpremHostModel) Expand(ctx context.Context, diags *diag.Diagnosti ConfigBgp: ExpandProtoBgpConfig(ctx, m.ConfigBgp, diags), ConfigOspf: ExpandProtoOspfConfig(ctx, m.ConfigOspf, diags), ConfigOspfv3: ExpandProtoOspfv3Config(ctx, m.ConfigOspfv3, diags), - CreatedAt: flex.ExpandTimePointer(ctx, m.CreatedAt, diags), IpAddress: flex.ExpandStringPointer(m.IpAddress), Ipv6Address: flex.ExpandStringPointer(m.Ipv6Address), Name: flex.ExpandStringPointer(m.Name), - UpdatedAt: flex.ExpandTimePointer(ctx, m.UpdatedAt, diags), + Id: flex.ExpandInt64Pointer(m.Id), } return to } -func FlattenProtoOnpremHost(ctx context.Context, from *anycast.OnpremHost, diags *diag.Diagnostics) types.Object { - if from == nil { - return types.ObjectNull(ProtoOnpremHostAttrTypes) - } - m := ProtoOnpremHostModel{} - m.Flatten(ctx, from, diags) - t, d := types.ObjectValueFrom(ctx, ProtoOnpremHostAttrTypes, m) - diags.Append(d...) - return t -} - func (m *ProtoOnpremHostModel) Flatten(ctx context.Context, from *anycast.OnpremHost, diags *diag.Diagnostics) { if from == nil { return diff --git a/internal/service/anycast/model_proto_onprem_host_ref.go b/internal/service/anycast/model_proto_onprem_host_ref.go index bb138bcc..67c8e64a 100644 --- a/internal/service/anycast/model_proto_onprem_host_ref.go +++ b/internal/service/anycast/model_proto_onprem_host_ref.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" schema "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/planmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -34,8 +36,10 @@ var ProtoOnpremHostRefAttrTypes = map[string]attr.Type{ var ProtoOnpremHostRefResourceSchemaAttributes = map[string]schema.Attribute{ "id": schema.Int64Attribute{ - Optional: true, - Computed: true, + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, MarkdownDescription: "The resource identifier.", }, "ip_address": schema.StringAttribute{ @@ -49,7 +53,6 @@ var ProtoOnpremHostRefResourceSchemaAttributes = map[string]schema.Attribute{ MarkdownDescription: "IPv6 address of the host in string format", }, "name": schema.StringAttribute{ - Optional: true, Computed: true, MarkdownDescription: `The name of the anycast.`, }, @@ -58,7 +61,8 @@ var ProtoOnpremHostRefResourceSchemaAttributes = map[string]schema.Attribute{ MarkdownDescription: "Unique 32-character string identifier assigned to the host", }, "runtime_status": schema.StringAttribute{ - Computed: true, + Computed: true, + MarkdownDescription: "The runtime status of the host", }, } diff --git a/internal/service/anycast/model_proto_ospf_config.go b/internal/service/anycast/model_proto_ospf_config.go index 29f1ace6..b36a7dcb 100644 --- a/internal/service/anycast/model_proto_ospf_config.go +++ b/internal/service/anycast/model_proto_ospf_config.go @@ -50,25 +50,32 @@ var ProtoOspfConfigResourceSchemaAttributes = map[string]schema.Attribute{ MarkdownDescription: "OSPF area identifier; usually in the format of an IPv4 address (although not an address itself)", }, "area_type": schema.StringAttribute{ - Optional: true, + Optional: true, + MarkdownDescription: `OSPF area type; one of: "STANDARD", "STUB", "NSSA".`, }, "authentication_key": schema.StringAttribute{ - Optional: true, + Optional: true, + MarkdownDescription: `OSPF authentication key.`, }, "authentication_key_id": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `title: Numeric OSPF authentication key identifier.`, }, "authentication_type": schema.StringAttribute{ - Optional: true, + Optional: true, + MarkdownDescription: `OSPF authentication type; one of "Clear", "MD5".`, }, "cost": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Explicit link cost for the interface.`, }, "dead_interval": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `OSPF router dead interval timer in seconds; must be the same for all the routers on the same network; default: 40 secs.`, }, "hello_interval": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Period (in seconds) of OSPF Hello packet, sent by the OSPF router; must be the same for all the routers on the same network; default: 10 secs.`, }, "interface": schema.StringAttribute{ Optional: true, @@ -79,10 +86,12 @@ var ProtoOspfConfigResourceSchemaAttributes = map[string]schema.Attribute{ MarkdownDescription: "Any predefined OSPF configuration, with embedded new lines; the preamble will be prepended to the generated BGP configuration.", }, "retransmit_interval": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Period (in seconds) of retransmitting for OSPF Database Description and Link State Requests; default: 5 seconds.`, }, "transmit_delay": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Estimated time to transmit link state advertisements; default: 1 sec.`, }, } diff --git a/internal/service/anycast/model_proto_ospfv3_config.go b/internal/service/anycast/model_proto_ospfv3_config.go index dbbdb568..5de16909 100644 --- a/internal/service/anycast/model_proto_ospfv3_config.go +++ b/internal/service/anycast/model_proto_ospfv3_config.go @@ -40,23 +40,28 @@ var ProtoOspfv3ConfigResourceSchemaAttributes = map[string]schema.Attribute{ MarkdownDescription: "OSPF area identifier; usually in the format of an IPv4 address (although not an address itself)", }, "cost": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Explicit link cost for the interface.`, }, "dead_interval": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `OSPF router dead interval timer in seconds; must be the same for all the routers on the same network; default: 40 sec.`, }, "hello_interval": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Period (in seconds) of OSPF Hello packet, sent by the OSPF router; must be the same for all the routers on the same network; default: 10 secs.`, }, "interface": schema.StringAttribute{ Optional: true, MarkdownDescription: "Name of the interface that is configured with external IP address of the host", }, "retransmit_interval": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Period (in seconds) of retransmitting for OSPF Database Description and Link State Requests; default: 5 seconds.`, }, "transmit_delay": schema.Int64Attribute{ - Optional: true, + Optional: true, + MarkdownDescription: `Estimated time to transmit link state advertisements; default: 1 sec.`, }, }