diff --git a/docs/data-sources/cdn_custom_domain.md b/docs/data-sources/cdn_custom_domain.md
index 38c2da3e..7839b991 100644
--- a/docs/data-sources/cdn_custom_domain.md
+++ b/docs/data-sources/cdn_custom_domain.md
@@ -4,14 +4,14 @@ page_title: "stackit_cdn_custom_domain Data Source - stackit"
subcategory: ""
description: |-
CDN distribution data source schema.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_cdn_custom_domain (Data Source)
CDN distribution data source schema.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/data-sources/cdn_distribution.md b/docs/data-sources/cdn_distribution.md
index 641e9698..799d29c2 100644
--- a/docs/data-sources/cdn_distribution.md
+++ b/docs/data-sources/cdn_distribution.md
@@ -4,14 +4,14 @@ page_title: "stackit_cdn_distribution Data Source - stackit"
subcategory: ""
description: |-
CDN distribution data source schema.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_cdn_distribution (Data Source)
CDN distribution data source schema.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/data-sources/git.md b/docs/data-sources/git.md
index c4865611..8cbd29ef 100644
--- a/docs/data-sources/git.md
+++ b/docs/data-sources/git.md
@@ -4,14 +4,14 @@ page_title: "stackit_git Data Source - stackit"
subcategory: ""
description: |-
Git Instance datasource schema.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_git (Data Source)
Git Instance datasource schema.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/data-sources/routing_table.md b/docs/data-sources/routing_table.md
new file mode 100644
index 00000000..748431d9
--- /dev/null
+++ b/docs/data-sources/routing_table.md
@@ -0,0 +1,48 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_routing_table Data Source - stackit"
+subcategory: ""
+description: |-
+ Routing table datasource schema. Must have a region specified in the provider configuration.
+ ~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+---
+
+# stackit_routing_table (Data Source)
+
+Routing table datasource schema. Must have a `region` specified in the provider configuration.
+
+~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+
+## Example Usage
+
+```terraform
+data "stackit_routing_table" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_area_id` (String) The network area ID to which the routing table is associated.
+- `organization_id` (String) STACKIT organization ID to which the routing table is associated.
+- `routing_table_id` (String) The routing tables ID.
+
+### Optional
+
+- `region` (String) The resource region. If not defined, the provider region is used.
+
+### Read-Only
+
+- `created_at` (String) Date-time when the routing table was created
+- `default` (Boolean) When true this is the default routing table for this network area. It can't be deleted and is used if the user does not specify it otherwise.
+- `description` (String) Description of the routing table.
+- `id` (String) Terraform's internal datasource ID. It is structured as "`organization_id`,`region`,`network_area_id`,`routing_table_id`".
+- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
+- `name` (String) The name of the routing table.
+- `system_routes` (Boolean)
+- `updated_at` (String) Date-time when the routing table was updated
diff --git a/docs/data-sources/routing_table_route.md b/docs/data-sources/routing_table_route.md
new file mode 100644
index 00000000..a637284b
--- /dev/null
+++ b/docs/data-sources/routing_table_route.md
@@ -0,0 +1,65 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_routing_table_route Data Source - stackit"
+subcategory: ""
+description: |-
+ Routing table route datasource schema. Must have a region specified in the provider configuration.
+ ~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+---
+
+# stackit_routing_table_route (Data Source)
+
+Routing table route datasource schema. Must have a `region` specified in the provider configuration.
+
+~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+
+## Example Usage
+
+```terraform
+data "stackit_routing_table_route" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ route_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_area_id` (String) The network area ID to which the routing table is associated.
+- `organization_id` (String) STACKIT organization ID to which the routing table is associated.
+- `route_id` (String) Route ID.
+- `routing_table_id` (String) The routing tables ID.
+
+### Optional
+
+- `region` (String) The resource region. If not defined, the provider region is used.
+
+### Read-Only
+
+- `created_at` (String) Date-time when the route was created
+- `destination` (Attributes) Destination of the route. (see [below for nested schema](#nestedatt--destination))
+- `id` (String) Terraform's internal datasource ID. It is structured as "`organization_id`,`region`,`network_area_id`,`routing_table_id`,`route_id`".
+- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
+- `next_hop` (Attributes) Next hop destination. (see [below for nested schema](#nestedatt--next_hop))
+- `updated_at` (String) Date-time when the route was updated
+
+
+### Nested Schema for `destination`
+
+Read-Only:
+
+- `type` (String) CIDRV type. Possible values are: `cidrv4`, `cidrv6`. Only `cidrv4` is supported during experimental stage.
+- `value` (String) An CIDR string.
+
+
+
+### Nested Schema for `next_hop`
+
+Read-Only:
+
+- `type` (String) Possible values are: `blackhole`, `internet`, `ipv4`, `ipv6`. Only `cidrv4` is supported during experimental stage..
+- `value` (String) Either IPv4 or IPv6 (not set for blackhole and internet). Only IPv4 supported during experimental stage.
diff --git a/docs/data-sources/routing_table_routes.md b/docs/data-sources/routing_table_routes.md
new file mode 100644
index 00000000..0d79e464
--- /dev/null
+++ b/docs/data-sources/routing_table_routes.md
@@ -0,0 +1,71 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_routing_table_routes Data Source - stackit"
+subcategory: ""
+description: |-
+ Routing table routes datasource schema. Must have a region specified in the provider configuration.
+ ~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+---
+
+# stackit_routing_table_routes (Data Source)
+
+Routing table routes datasource schema. Must have a `region` specified in the provider configuration.
+
+~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+
+## Example Usage
+
+```terraform
+data "stackit_routing_table_routes" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_area_id` (String) The network area ID to which the routing table is associated.
+- `organization_id` (String) STACKIT organization ID to which the routing table is associated.
+- `routing_table_id` (String) The routing tables ID.
+
+### Optional
+
+- `region` (String) The datasource region. If not defined, the provider region is used.
+
+### Read-Only
+
+- `id` (String) Terraform's internal datasource ID. It is structured as "`organization_id`,`region`,`network_area_id`,`routing_table_id`,`route_id`".
+- `routes` (Attributes List) List of routes. (see [below for nested schema](#nestedatt--routes))
+
+
+### Nested Schema for `routes`
+
+Read-Only:
+
+- `created_at` (String) Date-time when the route was created
+- `destination` (Attributes) Destination of the route. (see [below for nested schema](#nestedatt--routes--destination))
+- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
+- `next_hop` (Attributes) Next hop destination. (see [below for nested schema](#nestedatt--routes--next_hop))
+- `route_id` (String) Route ID.
+- `updated_at` (String) Date-time when the route was updated
+
+
+### Nested Schema for `routes.destination`
+
+Read-Only:
+
+- `type` (String) CIDRV type. Possible values are: `cidrv4`, `cidrv6`. Only `cidrv4` is supported during experimental stage.
+- `value` (String) An CIDR string.
+
+
+
+### Nested Schema for `routes.next_hop`
+
+Read-Only:
+
+- `type` (String) Possible values are: `blackhole`, `internet`, `ipv4`, `ipv6`. Only `cidrv4` is supported during experimental stage..
+- `value` (String) Either IPv4 or IPv6 (not set for blackhole and internet). Only IPv4 supported during experimental stage.
diff --git a/docs/data-sources/routing_tables.md b/docs/data-sources/routing_tables.md
new file mode 100644
index 00000000..787a3261
--- /dev/null
+++ b/docs/data-sources/routing_tables.md
@@ -0,0 +1,54 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_routing_tables Data Source - stackit"
+subcategory: ""
+description: |-
+ Routing table datasource schema. Must have a region specified in the provider configuration.
+ ~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+---
+
+# stackit_routing_tables (Data Source)
+
+Routing table datasource schema. Must have a `region` specified in the provider configuration.
+
+~> This datasource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+
+## Example Usage
+
+```terraform
+data "stackit_routing_tables" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_area_id` (String) The network area ID to which the routing table is associated.
+- `organization_id` (String) STACKIT organization ID to which the routing table is associated.
+
+### Optional
+
+- `region` (String) The resource region. If not defined, the provider region is used.
+
+### Read-Only
+
+- `id` (String) Terraform's internal datasource ID. It is structured as "`organization_id`,`region`,`network_area_id`".
+- `items` (Attributes List) List of routing tables. (see [below for nested schema](#nestedatt--items))
+
+
+### Nested Schema for `items`
+
+Read-Only:
+
+- `created_at` (String) Date-time when the routing table was created
+- `default` (Boolean) When true this is the default routing table for this network area. It can't be deleted and is used if the user does not specify it otherwise.
+- `description` (String) Description of the routing table.
+- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
+- `name` (String) The name of the routing table.
+- `routing_table_id` (String) The routing tables ID.
+- `system_routes` (Boolean)
+- `updated_at` (String) Date-time when the routing table was updated
diff --git a/docs/data-sources/server_backup_schedule.md b/docs/data-sources/server_backup_schedule.md
index 0d5422a2..16126086 100644
--- a/docs/data-sources/server_backup_schedule.md
+++ b/docs/data-sources/server_backup_schedule.md
@@ -4,14 +4,14 @@ page_title: "stackit_server_backup_schedule Data Source - stackit"
subcategory: ""
description: |-
Server backup schedule datasource schema. Must have a region specified in the provider configuration.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_server_backup_schedule (Data Source)
Server backup schedule datasource schema. Must have a `region` specified in the provider configuration.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/data-sources/server_backup_schedules.md b/docs/data-sources/server_backup_schedules.md
index 30b3cf9b..44c21612 100644
--- a/docs/data-sources/server_backup_schedules.md
+++ b/docs/data-sources/server_backup_schedules.md
@@ -4,14 +4,14 @@ page_title: "stackit_server_backup_schedules Data Source - stackit"
subcategory: ""
description: |-
Server backup schedules datasource schema. Must have a region specified in the provider configuration.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_server_backup_schedules (Data Source)
Server backup schedules datasource schema. Must have a `region` specified in the provider configuration.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/data-sources/server_update_schedule.md b/docs/data-sources/server_update_schedule.md
index 62a10db0..ff4a0c4a 100644
--- a/docs/data-sources/server_update_schedule.md
+++ b/docs/data-sources/server_update_schedule.md
@@ -4,14 +4,14 @@ page_title: "stackit_server_update_schedule Data Source - stackit"
subcategory: ""
description: |-
Server update schedule datasource schema. Must have a region specified in the provider configuration.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_server_update_schedule (Data Source)
Server update schedule datasource schema. Must have a `region` specified in the provider configuration.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/data-sources/server_update_schedules.md b/docs/data-sources/server_update_schedules.md
index cf34faae..2ccfe2b5 100644
--- a/docs/data-sources/server_update_schedules.md
+++ b/docs/data-sources/server_update_schedules.md
@@ -4,14 +4,14 @@ page_title: "stackit_server_update_schedules Data Source - stackit"
subcategory: ""
description: |-
Server update schedules datasource schema. Must have a region specified in the provider configuration.
- ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+ ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
---
# stackit_server_update_schedules (Data Source)
Server update schedules datasource schema. Must have a `region` specified in the provider configuration.
-~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
## Example Usage
diff --git a/docs/index.md b/docs/index.md
index 51aee8c5..d2eb290e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -157,7 +157,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
- `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global
- `dns_custom_endpoint` (String) Custom endpoint for the DNS service
- `enable_beta_resources` (Boolean) Enable beta resources. Default is false.
-- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: [iam]
+- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: [iam routing-tables]
- `git_custom_endpoint` (String) Custom endpoint for the Git service
- `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service
- `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service
diff --git a/docs/resources/routing_table.md b/docs/resources/routing_table.md
new file mode 100644
index 00000000..bd6c7e72
--- /dev/null
+++ b/docs/resources/routing_table.md
@@ -0,0 +1,50 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_routing_table Resource - stackit"
+subcategory: ""
+description: |-
+ Routing table resource schema. Must have a region specified in the provider configuration.
+ ~> This resource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+---
+
+# stackit_routing_table (Resource)
+
+Routing table resource schema. Must have a `region` specified in the provider configuration.
+
+~> This resource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+
+## Example Usage
+
+```terraform
+resource "stackit_routing_table" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example"
+ labels = {
+ "key" = "value"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name of the routing table.
+- `network_area_id` (String) The network area ID to which the routing table is associated.
+- `organization_id` (String) STACKIT organization ID to which the routing table is associated.
+
+### Optional
+
+- `description` (String) Description of the routing table.
+- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
+- `region` (String) The resource region. If not defined, the provider region is used.
+- `system_routes` (Boolean)
+
+### Read-Only
+
+- `created_at` (String) Date-time when the routing table was created
+- `id` (String) Terraform's internal resource ID. It is structured as "`organization_id`,`region`,`network_area_id`,`routing_table_id`".
+- `routing_table_id` (String) The routing tables ID.
+- `updated_at` (String) Date-time when the routing table was updated
diff --git a/docs/resources/routing_table_route.md b/docs/resources/routing_table_route.md
new file mode 100644
index 00000000..5b37be89
--- /dev/null
+++ b/docs/resources/routing_table_route.md
@@ -0,0 +1,78 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_routing_table_route Resource - stackit"
+subcategory: ""
+description: |-
+ Routing table route resource schema. Must have a region specified in the provider configuration.
+ ~> This resource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+---
+
+# stackit_routing_table_route (Resource)
+
+Routing table route resource schema. Must have a `region` specified in the provider configuration.
+
+~> This resource is part of the routing-tables experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
+
+## Example Usage
+
+```terraform
+resource "stackit_routing_table_route" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ destination = {
+ type = "cidrv4"
+ value = "192.168.178.0/24"
+ }
+ next_hop = {
+ type = "ipv4"
+ value = "192.168.178.1"
+ }
+ labels = {
+ "key" = "value"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `destination` (Attributes) Destination of the route. (see [below for nested schema](#nestedatt--destination))
+- `network_area_id` (String) The network area ID to which the routing table is associated.
+- `next_hop` (Attributes) Next hop destination. (see [below for nested schema](#nestedatt--next_hop))
+- `organization_id` (String) STACKIT organization ID to which the routing table is associated.
+- `routing_table_id` (String) The routing tables ID.
+
+### Optional
+
+- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
+- `region` (String) The resource region. If not defined, the provider region is used.
+
+### Read-Only
+
+- `created_at` (String) Date-time when the route was created.
+- `id` (String) Terraform's internal resource ID. It is structured as "`organization_id`,`region`,`network_area_id`,`routing_table_id`,`route_id`".
+- `route_id` (String) The ID of the route.
+- `updated_at` (String) Date-time when the route was updated.
+
+
+### Nested Schema for `destination`
+
+Required:
+
+- `type` (String) CIDRV type. Possible values are: `cidrv4`, `cidrv6`. Only `cidrv4` is supported during experimental stage.
+- `value` (String) An CIDR string.
+
+
+
+### Nested Schema for `next_hop`
+
+Required:
+
+- `type` (String) Possible values are: `blackhole`, `internet`, `ipv4`, `ipv6`. Only `cidrv4` is supported during experimental stage..
+
+Optional:
+
+- `value` (String) Either IPv4 or IPv6 (not set for blackhole and internet). Only IPv4 supported during experimental stage.
diff --git a/examples/data-sources/stackit_routing_table/data-source.tf b/examples/data-sources/stackit_routing_table/data-source.tf
new file mode 100644
index 00000000..575ab17d
--- /dev/null
+++ b/examples/data-sources/stackit_routing_table/data-source.tf
@@ -0,0 +1,5 @@
+data "stackit_routing_table" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
diff --git a/examples/data-sources/stackit_routing_table_route/data-source.tf b/examples/data-sources/stackit_routing_table_route/data-source.tf
new file mode 100644
index 00000000..630b9dec
--- /dev/null
+++ b/examples/data-sources/stackit_routing_table_route/data-source.tf
@@ -0,0 +1,6 @@
+data "stackit_routing_table_route" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ route_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
diff --git a/examples/data-sources/stackit_routing_table_routes/data-source.tf b/examples/data-sources/stackit_routing_table_routes/data-source.tf
new file mode 100644
index 00000000..badf79c3
--- /dev/null
+++ b/examples/data-sources/stackit_routing_table_routes/data-source.tf
@@ -0,0 +1,5 @@
+data "stackit_routing_table_routes" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
diff --git a/examples/data-sources/stackit_routing_tables/data-source.tf b/examples/data-sources/stackit_routing_tables/data-source.tf
new file mode 100644
index 00000000..f71527d7
--- /dev/null
+++ b/examples/data-sources/stackit_routing_tables/data-source.tf
@@ -0,0 +1,4 @@
+data "stackit_routing_tables" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+}
diff --git a/examples/resources/stackit_routing_table/resource.tf b/examples/resources/stackit_routing_table/resource.tf
new file mode 100644
index 00000000..8841401f
--- /dev/null
+++ b/examples/resources/stackit_routing_table/resource.tf
@@ -0,0 +1,8 @@
+resource "stackit_routing_table" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example"
+ labels = {
+ "key" = "value"
+ }
+}
diff --git a/examples/resources/stackit_routing_table_route/resource.tf b/examples/resources/stackit_routing_table_route/resource.tf
new file mode 100644
index 00000000..820da292
--- /dev/null
+++ b/examples/resources/stackit_routing_table_route/resource.tf
@@ -0,0 +1,16 @@
+resource "stackit_routing_table_route" "example" {
+ organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ network_area_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ routing_table_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ destination = {
+ type = "cidrv4"
+ value = "192.168.178.0/24"
+ }
+ next_hop = {
+ type = "ipv4"
+ value = "192.168.178.1"
+ }
+ labels = {
+ "key" = "value"
+ }
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 9aa54d43..4f01ddac 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.0
github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0
+ github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.19-alpha
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0
diff --git a/go.sum b/go.sum
index f518c651..c2bcef40 100644
--- a/go.sum
+++ b/go.sum
@@ -162,6 +162,8 @@ github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0 h1:C+8z3MdvnTngcH9L72
github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0/go.mod h1:agI7SONeLR/IZL3TOgn1tDzfS63O2rWKQE8+huRjEzU=
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0 h1:7qm/Tft79wFlHomPdgjUJ9uJU8kEk+k9ficMGRoHtf0=
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0/go.mod h1:lUGkcbyMkd4nRBDFmKohIwlgtOZqQo4Ek5S5ajw90Xg=
+github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.19-alpha h1:HnQyJSXbtYzN9IhTO02zxLrcSxyauIbeJD+GTf23A50=
+github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.19-alpha/go.mod h1:Wt77ucOwpe9g/84LijU+YhWbn3vLcpkAoRy2i+FobNQ=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0 h1:Ef4SyTBjIkfwaws4mssa6AoK+OokHFtr7ZIflUpoXVE=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0/go.mod h1:FiVhDlw9+yuTiUmnyGLn2qpsLW26w9OC4TS1y78czvg=
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 h1:QKOfaB7EcuJmBCxpFXN2K7g2ih0gQM6cyZ1VhTmtQfI=
diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go
index 293afa32..e477c906 100644
--- a/stackit/internal/core/core.go
+++ b/stackit/internal/core/core.go
@@ -15,6 +15,13 @@ import (
// Separator used for concatenation of TF-internal resource ID
const Separator = ","
+type ResourceType string
+
+const (
+ Resource ResourceType = "resource"
+ Datasource ResourceType = "datasource"
+)
+
type ProviderData struct {
RoundTripper http.RoundTripper
ServiceAccountEmail string // Deprecated: ServiceAccountEmail is not required and will be removed after 12th June 2025.
@@ -100,14 +107,14 @@ func LogAndAddWarning(ctx context.Context, diags *diag.Diagnostics, summary, det
diags.AddWarning(summary, detail)
}
-func LogAndAddWarningBeta(ctx context.Context, diags *diag.Diagnostics, name, resourceType string) {
+func LogAndAddWarningBeta(ctx context.Context, diags *diag.Diagnostics, name string, resourceType ResourceType) {
warnTitle := fmt.Sprintf("The %s %q is in beta", resourceType, name)
warnContent := fmt.Sprintf("The %s %q is in beta and may be subject to breaking changes in the future. Use with caution.", resourceType, name)
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
diags.AddWarning(warnTitle, warnContent)
}
-func LogAndAddErrorBeta(ctx context.Context, diags *diag.Diagnostics, name, resourceType string) {
+func LogAndAddErrorBeta(ctx context.Context, diags *diag.Diagnostics, name string, resourceType ResourceType) {
errTitle := fmt.Sprintf("The %s %q is in beta and beta is not enabled", resourceType, name)
errContent := fmt.Sprintf(`The %s %q is in beta and the beta functionality is currently not enabled. To enable it, set the environment variable STACKIT_TF_ENABLE_BETA_RESOURCES to "true" or set the "enable_beta_resources" provider field to true.`, resourceType, name)
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
diff --git a/stackit/internal/features/beta.go b/stackit/internal/features/beta.go
index afc599ed..4354fbd5 100644
--- a/stackit/internal/features/beta.go
+++ b/stackit/internal/features/beta.go
@@ -39,7 +39,7 @@ Defaulting to the provider feature flag.`, value)
//
// Should be called in the Configure method of a beta resource.
// Then, check for Errors in the diags using the diags.HasError() method.
-func CheckBetaResourcesEnabled(ctx context.Context, data *core.ProviderData, diags *diag.Diagnostics, resourceName, resourceType string) {
+func CheckBetaResourcesEnabled(ctx context.Context, data *core.ProviderData, diags *diag.Diagnostics, resourceName string, resourceType core.ResourceType) {
if !BetaResourcesEnabled(ctx, data, diags) {
core.LogAndAddErrorBeta(ctx, diags, resourceName, resourceType)
return
@@ -47,11 +47,11 @@ func CheckBetaResourcesEnabled(ctx context.Context, data *core.ProviderData, dia
core.LogAndAddWarningBeta(ctx, diags, resourceName, resourceType)
}
-func AddBetaDescription(description string) string {
+func AddBetaDescription(description string, resourceType core.ResourceType) string {
// Callout block: https://developer.hashicorp.com/terraform/registry/providers/docs#callouts
return fmt.Sprintf("%s\n\n~> %s %s",
description,
- "This resource is in beta and may be subject to breaking changes in the future. Use with caution.",
+ fmt.Sprintf("This %s is in beta and may be subject to breaking changes in the future. Use with caution.", resourceType),
"See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.",
)
}
diff --git a/stackit/internal/features/experiments.go b/stackit/internal/features/experiments.go
index aee7debe..a2d2f622 100644
--- a/stackit/internal/features/experiments.go
+++ b/stackit/internal/features/experiments.go
@@ -11,7 +11,11 @@ import (
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
)
-var AvailableExperiments []string = []string{"iam"}
+const (
+ RoutingTablesExperiment = "routing-tables"
+)
+
+var AvailableExperiments = []string{"iam", RoutingTablesExperiment}
// Check if an experiment is valid.
func ValidExperiment(experiment string, diags *diag.Diagnostics) bool {
@@ -26,7 +30,7 @@ func ValidExperiment(experiment string, diags *diag.Diagnostics) bool {
}
// Check if an experiment is enabled.
-func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experiment, resourceType string, diags *diag.Diagnostics) {
+func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experiment, resourceName string, resourceType core.ResourceType, diags *diag.Diagnostics) {
if !ValidExperiment(experiment, diags) {
errTitle := fmt.Sprintf("The experiment %s does not exist.", experiment)
errContent := "This is a bug in the STACKIT Terraform Provider. Please open an issue here: https://github.com/stackitcloud/terraform-provider-stackit/issues"
@@ -38,23 +42,25 @@ func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experi
})
if experimentActive {
- warnTitle := fmt.Sprintf("%s is part of the %s experiment.", resourceType, experiment)
- warnContent := fmt.Sprintf("This resource is part of the %s experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.", experiment)
+ warnTitle := fmt.Sprintf("%s is part of the %s experiment.", resourceName, experiment)
+ warnContent := fmt.Sprintf("This %s is part of the %s experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.", resourceType, experiment)
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
diags.AddWarning(warnTitle, warnContent)
return
}
- errTitle := fmt.Sprintf("%s is part of the %s experiment, which is currently disabled by default", resourceType, experiment)
+ errTitle := fmt.Sprintf("%s is part of the %s experiment, which is currently disabled by default", resourceName, experiment)
errContent := fmt.Sprintf(`Enable the %s experiment by adding it into your provider block.`, experiment)
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
diags.AddError(errTitle, errContent)
}
-func AddExperimentDescription(description, experiment string) string {
+func AddExperimentDescription(description, experiment string, resourceType core.ResourceType) string {
// Callout block: https://developer.hashicorp.com/terraform/registry/providers/docs#callouts
- return fmt.Sprintf("%s\n\n~> %s%s%s",
+ return fmt.Sprintf("%s\n\n~> %s%s%s%s%s",
description,
- "This resource is part of the ",
+ "This ",
+ resourceType,
+ " is part of the ",
experiment,
" experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.",
)
diff --git a/stackit/internal/features/experiments_test.go b/stackit/internal/features/experiments_test.go
index bf26f3b9..392469ae 100644
--- a/stackit/internal/features/experiments_test.go
+++ b/stackit/internal/features/experiments_test.go
@@ -49,7 +49,8 @@ func TestCheckExperimentEnabled(t *testing.T) {
ctx context.Context
data *core.ProviderData
experiment string
- resourceType string
+ resourceName string
+ resourceType core.ResourceType
diags *diag.Diagnostics
}
tests := []struct {
@@ -65,8 +66,9 @@ func TestCheckExperimentEnabled(t *testing.T) {
data: &core.ProviderData{
Experiments: []string{"iam"},
},
- experiment: "iam",
- diags: &diag.Diagnostics{},
+ experiment: "iam",
+ resourceType: core.Resource,
+ diags: &diag.Diagnostics{},
},
wantDiagsErr: false,
wantDiagsWarning: true,
@@ -78,8 +80,9 @@ func TestCheckExperimentEnabled(t *testing.T) {
data: &core.ProviderData{
Experiments: []string{},
},
- experiment: "iam",
- diags: &diag.Diagnostics{},
+ experiment: "iam",
+ resourceType: core.Resource,
+ diags: &diag.Diagnostics{},
},
wantDiagsErr: true,
wantDiagsWarning: false,
@@ -92,7 +95,7 @@ func TestCheckExperimentEnabled(t *testing.T) {
Experiments: []string{"iam"},
},
experiment: "foobar",
- resourceType: "provider",
+ resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantDiagsErr: true,
@@ -101,7 +104,7 @@ func TestCheckExperimentEnabled(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- CheckExperimentEnabled(tt.args.ctx, tt.args.data, tt.args.experiment, tt.args.resourceType, tt.args.diags)
+ CheckExperimentEnabled(tt.args.ctx, tt.args.data, tt.args.experiment, tt.args.resourceName, tt.args.resourceType, tt.args.diags)
if got := tt.args.diags.HasError(); got != tt.wantDiagsErr {
t.Errorf("CheckExperimentEnabled() diags.HasError() = %v, want %v", got, tt.wantDiagsErr)
}
diff --git a/stackit/internal/services/authorization/roleassignments/resource.go b/stackit/internal/services/authorization/roleassignments/resource.go
index 0bfc2f6d..7b106bff 100644
--- a/stackit/internal/services/authorization/roleassignments/resource.go
+++ b/stackit/internal/services/authorization/roleassignments/resource.go
@@ -84,7 +84,7 @@ func (r *roleAssignmentResource) Configure(ctx context.Context, req resource.Con
return
}
- features.CheckExperimentEnabled(ctx, &providerData, experiment, fmt.Sprintf("stackit_authorization_%s_role_assignment", r.apiName), &resp.Diagnostics)
+ features.CheckExperimentEnabled(ctx, &providerData, experiment, fmt.Sprintf("stackit_authorization_%s_role_assignment", r.apiName), core.Resource, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
@@ -100,7 +100,7 @@ func (r *roleAssignmentResource) Configure(ctx context.Context, req resource.Con
// Schema defines the schema for the resource.
func (r *roleAssignmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
- "main": features.AddExperimentDescription(fmt.Sprintf("%s Role Assignment resource schema.", r.apiName), experiment),
+ "main": features.AddExperimentDescription(fmt.Sprintf("%s Role Assignment resource schema.", r.apiName), experiment, core.Resource),
"id": "Terraform's internal resource identifier. It is structured as \"[resource_id],[role],[subject]\".",
"resource_id": fmt.Sprintf("%s Resource to assign the role to.", r.apiName),
"role": "Role to be assigned",
diff --git a/stackit/internal/services/cdn/customdomain/datasource.go b/stackit/internal/services/cdn/customdomain/datasource.go
index acf3f4df..19bb27f3 100644
--- a/stackit/internal/services/cdn/customdomain/datasource.go
+++ b/stackit/internal/services/cdn/customdomain/datasource.go
@@ -41,7 +41,7 @@ func (d *customDomainDataSource) Configure(ctx context.Context, req datasource.C
return
}
- features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_cdn_custom_domain", "datasource")
+ features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_cdn_custom_domain", core.Datasource)
if resp.Diagnostics.HasError() {
return
}
@@ -60,7 +60,7 @@ func (r *customDomainDataSource) Metadata(_ context.Context, req datasource.Meta
func (r *customDomainDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
- MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema."),
+ MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema.", core.Datasource),
Description: "CDN distribution data source schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
diff --git a/stackit/internal/services/cdn/customdomain/resource.go b/stackit/internal/services/cdn/customdomain/resource.go
index 45a32604..5e7bc94d 100644
--- a/stackit/internal/services/cdn/customdomain/resource.go
+++ b/stackit/internal/services/cdn/customdomain/resource.go
@@ -88,7 +88,7 @@ func (r *customDomainResource) Metadata(_ context.Context, req resource.Metadata
func (r *customDomainResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema."),
+ MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema.", core.Resource),
Description: "CDN distribution data source schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
diff --git a/stackit/internal/services/cdn/distribution/datasource.go b/stackit/internal/services/cdn/distribution/datasource.go
index cfad1f5a..d692f3e3 100644
--- a/stackit/internal/services/cdn/distribution/datasource.go
+++ b/stackit/internal/services/cdn/distribution/datasource.go
@@ -58,7 +58,7 @@ func (r *distributionDataSource) Metadata(_ context.Context, req datasource.Meta
func (r *distributionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
backendOptions := []string{"http"}
resp.Schema = schema.Schema{
- MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema."),
+ MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema.", core.Datasource),
Description: "CDN distribution data source schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
diff --git a/stackit/internal/services/cdn/distribution/resource.go b/stackit/internal/services/cdn/distribution/resource.go
index 39b66310..22e4af62 100644
--- a/stackit/internal/services/cdn/distribution/resource.go
+++ b/stackit/internal/services/cdn/distribution/resource.go
@@ -137,7 +137,7 @@ func (r *distributionResource) Metadata(_ context.Context, req resource.Metadata
func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
backendOptions := []string{"http"}
resp.Schema = schema.Schema{
- MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema."),
+ MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema.", core.Resource),
Description: "CDN distribution data source schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
diff --git a/stackit/internal/services/git/instance/datasource.go b/stackit/internal/services/git/instance/datasource.go
index 48931d77..b54d189a 100644
--- a/stackit/internal/services/git/instance/datasource.go
+++ b/stackit/internal/services/git/instance/datasource.go
@@ -63,7 +63,7 @@ func (g *gitDataSource) Metadata(_ context.Context, req datasource.MetadataReque
// Schema defines the schema for the git data source.
func (g *gitDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
- MarkdownDescription: features.AddBetaDescription("Git Instance datasource schema."),
+ MarkdownDescription: features.AddBetaDescription("Git Instance datasource schema.", core.Datasource),
Description: "Git Instance datasource schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
diff --git a/stackit/internal/services/git/instance/resource.go b/stackit/internal/services/git/instance/resource.go
index 19226bab..ccc963eb 100644
--- a/stackit/internal/services/git/instance/resource.go
+++ b/stackit/internal/services/git/instance/resource.go
@@ -94,7 +94,7 @@ func (g *gitResource) Metadata(_ context.Context, req resource.MetadataRequest,
// Schema defines the schema for the resource.
func (g *gitResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- MarkdownDescription: features.AddBetaDescription("Git Instance resource schema."),
+ MarkdownDescription: features.AddBetaDescription("Git Instance resource schema.", core.Resource),
Description: "Git Instance resource schema.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
diff --git a/stackit/internal/services/iaas/securitygrouprule/resource.go b/stackit/internal/services/iaas/securitygrouprule/resource.go
index c8e4722e..735e88cc 100644
--- a/stackit/internal/services/iaas/securitygrouprule/resource.go
+++ b/stackit/internal/services/iaas/securitygrouprule/resource.go
@@ -333,7 +333,7 @@ func (r *securityGroupRuleResource) Schema(_ context.Context, _ resource.SchemaR
},
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
- Description: fmt.Sprintf("The protocol name which the rule should match. Either `name` or `number` must be provided. %s", utils.FormatPossibleValues(protocolsPossibleValues)),
+ Description: fmt.Sprintf("The protocol name which the rule should match. Either `name` or `number` must be provided. %s", utils.FormatPossibleValues(protocolsPossibleValues...)),
Optional: true,
Computed: true,
Validators: []validator.String{
diff --git a/stackit/internal/services/iaasalpha/iaasalpha_acc_test.go b/stackit/internal/services/iaasalpha/iaasalpha_acc_test.go
new file mode 100644
index 00000000..2522a7fb
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/iaasalpha_acc_test.go
@@ -0,0 +1,832 @@
+package iaasalpha_test
+
+import (
+ "context"
+ _ "embed"
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+ "sync"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/config"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ stackitSdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
+ "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+
+ "maps"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
+)
+
+// TODO: create network area using terraform resource instead once it's out of experimental stage and GA
+const (
+ testNetworkAreaId = "25bbf23a-8134-4439-9f5e-1641caf8354e"
+)
+
+var (
+ //go:embed testdata/resource-routingtable-min.tf
+ resourceRoutingTableMinConfig string
+
+ //go:embed testdata/resource-routingtable-max.tf
+ resourceRoutingTableMaxConfig string
+
+ //go:embed testdata/resource-routingtable-route-min.tf
+ resourceRoutingTableRouteMinConfig string
+
+ //go:embed testdata/resource-routingtable-route-max.tf
+ resourceRoutingTableRouteMaxConfig string
+)
+
+var testConfigRoutingTableMin = config.Variables{
+ "organization_id": config.StringVariable(testutil.OrganizationId),
+ "network_area_id": config.StringVariable(testNetworkAreaId),
+ "name": config.StringVariable(fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
+}
+
+var testConfigRoutingTableMinUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testConfigRoutingTableMin)
+ updatedConfig["name"] = config.StringVariable(fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)))
+ return updatedConfig
+}()
+
+var testConfigRoutingTableMax = config.Variables{
+ "organization_id": config.StringVariable(testutil.OrganizationId),
+ "network_area_id": config.StringVariable(testNetworkAreaId),
+ "name": config.StringVariable(fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
+ "description": config.StringVariable("This is the description of the routing table."),
+ "label": config.StringVariable("routing-table-label-01"),
+ "system_routes": config.BoolVariable(false),
+ "region": config.StringVariable(testutil.Region),
+}
+
+var testConfigRoutingTableMaxUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ for k, v := range testConfigRoutingTableMax {
+ updatedConfig[k] = v
+ }
+ updatedConfig["name"] = config.StringVariable(fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)))
+ updatedConfig["description"] = config.StringVariable("This is the updated description of the routing table.")
+ updatedConfig["label"] = config.StringVariable("routing-table-updated-label-01")
+ return updatedConfig
+}()
+
+var testConfigRoutingTableRouteMin = config.Variables{
+ "organization_id": config.StringVariable(testutil.OrganizationId),
+ "network_area_id": config.StringVariable(testNetworkAreaId),
+ "routing_table_name": config.StringVariable(fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
+ "destination_type": config.StringVariable("cidrv4"),
+ "destination_value": config.StringVariable("192.168.178.0/24"),
+ "next_hop_type": config.StringVariable("ipv4"),
+ "next_hop_value": config.StringVariable("192.168.178.1"),
+}
+
+var testConfigRoutingTableRouteMinUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testConfigRoutingTableRouteMin)
+ // nothing possible to update of the required attributes...
+ return updatedConfig
+}()
+
+var testConfigRoutingTableRouteMax = config.Variables{
+ "organization_id": config.StringVariable(testutil.OrganizationId),
+ "network_area_id": config.StringVariable(testNetworkAreaId),
+ "routing_table_name": config.StringVariable(fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
+ "destination_type": config.StringVariable("cidrv4"), // TODO: use cidrv6 once it's supported as we already test cidrv4 in the min test
+ "destination_value": config.StringVariable("192.168.178.0/24"),
+ "next_hop_type": config.StringVariable("ipv4"), // TODO: use ipv6, internet or blackhole once they are supported as we already test ipv4 in the min test
+ "next_hop_value": config.StringVariable("192.168.178.1"),
+ "label": config.StringVariable("route-label-01"),
+}
+
+var testConfigRoutingTableRouteMaxUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testConfigRoutingTableRouteMax)
+ updatedConfig["label"] = config.StringVariable("route-updated-label-01")
+ return updatedConfig
+}()
+
+// execute routingtable and routingtable route min and max tests with t.Run() to prevent parallel runs (needed for tests of stackit_routing_tables datasource)
+func TestAccRoutingTable(t *testing.T) {
+ t.Run("TestAccRoutingTableMin", func(t *testing.T) {
+ t.Logf("TestAccRoutingTableMin name: %s", testutil.ConvertConfigVariable(testConfigRoutingTableMin["name"]))
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testConfigRoutingTableMin,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMinConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableMin["name"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.%", "0"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "region", testutil.Region),
+ resource.TestCheckNoResourceAttr("stackit_routing_table.routing_table", "description"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "system_routes", "true"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "updated_at"),
+ ),
+ },
+ // Data sources
+ {
+ ConfigVariables: testConfigRoutingTableMin,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ # single routing table
+ data "stackit_routing_table" "routing_table" {
+ organization_id = stackit_routing_table.routing_table.organization_id
+ network_area_id = stackit_routing_table.routing_table.network_area_id
+ routing_table_id = stackit_routing_table.routing_table.routing_table_id
+ }
+
+ # all routing tables in network area
+ data "stackit_routing_tables" "routing_tables" {
+ organization_id = stackit_routing_table.routing_table.organization_id
+ network_area_id = stackit_routing_table.routing_table.network_area_id
+ }
+ `,
+ testutil.IaaSProviderConfig(), resourceRoutingTableMinConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["network_area_id"])),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "data.stackit_routing_table.routing_table", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableMin["name"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "labels.%", "0"),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "region", testutil.Region),
+ resource.TestCheckNoResourceAttr("data.stackit_routing_table.routing_table", "description"),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "system_routes", "true"),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "default", "false"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "updated_at"),
+
+ // Routing tables
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["network_area_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "region", testutil.Region),
+ // there will be always two routing tables because of the main routing table of the network area
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.#", "2"),
+
+ // default routing table
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.0.default", "true"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.0.created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.0.updated_at"),
+
+ // second routing table managed via terraform
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "data.stackit_routing_tables.routing_tables", "items.1.routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.name", testutil.ConvertConfigVariable(testConfigRoutingTableMin["name"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.labels.%", "0"),
+ resource.TestCheckNoResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.description"),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.system_routes", "true"),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.default", "false"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.1.created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.1.updated_at"),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testConfigRoutingTableMinUpdated,
+ ResourceName: "stackit_routing_table.routing_table",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_routing_table.routing_table"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_routing_table.routing_table")
+ }
+ region, ok := r.Primary.Attributes["region"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute region")
+ }
+ networkAreaId, ok := r.Primary.Attributes["network_area_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute network_area_id")
+ }
+ routingTableId, ok := r.Primary.Attributes["routing_table_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute routing_table_id")
+ }
+ return fmt.Sprintf("%s,%s,%s,%s", testutil.OrganizationId, region, networkAreaId, routingTableId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testConfigRoutingTableMinUpdated,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMinConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMinUpdated["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMinUpdated["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableMinUpdated["name"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.%", "0"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "region", testutil.Region),
+ resource.TestCheckNoResourceAttr("stackit_routing_table.routing_table", "description"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "system_routes", "true"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "updated_at"),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+ })
+
+ t.Run("TestAccRoutingTableMax", func(t *testing.T) {
+ t.Logf("TestAccRoutingTableMax name: %s", testutil.ConvertConfigVariable(testConfigRoutingTableMax["name"]))
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testConfigRoutingTableMax,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMaxConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableMax["name"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.%", "1"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableMax["label"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "region", testutil.ConvertConfigVariable(testConfigRoutingTableMax["region"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "description", testutil.ConvertConfigVariable(testConfigRoutingTableMax["description"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "system_routes", testutil.ConvertConfigVariable(testConfigRoutingTableMax["system_routes"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "updated_at"),
+ ),
+ },
+ // Data sources
+ {
+ ConfigVariables: testConfigRoutingTableMax,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ # single routing table
+ data "stackit_routing_table" "routing_table" {
+ organization_id = stackit_routing_table.routing_table.organization_id
+ network_area_id = stackit_routing_table.routing_table.network_area_id
+ routing_table_id = stackit_routing_table.routing_table.routing_table_id
+ }
+
+ # all routing tables in network area
+ data "stackit_routing_tables" "routing_tables" {
+ organization_id = stackit_routing_table.routing_table.organization_id
+ network_area_id = stackit_routing_table.routing_table.network_area_id
+ }
+ `,
+ testutil.IaaSProviderConfig(), resourceRoutingTableMaxConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["network_area_id"])),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "data.stackit_routing_table.routing_table", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableMax["name"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "labels.%", "1"),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableMax["label"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "region", testutil.ConvertConfigVariable(testConfigRoutingTableMax["region"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "description", testutil.ConvertConfigVariable(testConfigRoutingTableMax["description"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "system_routes", testutil.ConvertConfigVariable(testConfigRoutingTableMax["system_routes"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "default", "false"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "updated_at"),
+
+ // Routing tables
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["network_area_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "region", testutil.ConvertConfigVariable(testConfigRoutingTableMax["region"])),
+ // there will be always two routing tables because of the main routing table of the network area
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.#", "2"),
+
+ // default routing table
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.0.default", "true"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.0.created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.0.updated_at"),
+
+ // second routing table managed via terraform
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "data.stackit_routing_tables.routing_tables", "items.1.routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.name", testutil.ConvertConfigVariable(testConfigRoutingTableMax["name"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.labels.%", "1"),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableMax["label"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.description", testutil.ConvertConfigVariable(testConfigRoutingTableMax["description"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.system_routes", testutil.ConvertConfigVariable(testConfigRoutingTableMax["system_routes"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_tables.routing_tables", "items.1.default", "false"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.1.created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_tables.routing_tables", "items.1.updated_at"),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testConfigRoutingTableMaxUpdated,
+ ResourceName: "stackit_routing_table.routing_table",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_routing_table.routing_table"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_routing_table.routing_table")
+ }
+ region, ok := r.Primary.Attributes["region"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute region")
+ }
+ networkAreaId, ok := r.Primary.Attributes["network_area_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute network_area_id")
+ }
+ routingTableId, ok := r.Primary.Attributes["routing_table_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute routing_table_id")
+ }
+ return fmt.Sprintf("%s,%s,%s,%s", testutil.OrganizationId, region, networkAreaId, routingTableId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testConfigRoutingTableMaxUpdated,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMaxConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["name"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.%", "1"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["label"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "region", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["region"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "description", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["description"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "system_routes", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["system_routes"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "updated_at"),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+ })
+
+ t.Run("TestAccRoutingTableRouteMin", func(t *testing.T) {
+ t.Logf("TestAccRoutingTableRouteMin")
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testConfigRoutingTableRouteMin,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMinConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["routing_table_name"])),
+
+ // Routing table route
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "routing_table_id"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "stackit_routing_table_route.route", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["destination_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["destination_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["next_hop_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["next_hop_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "labels.%", "0"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "updated_at"),
+ ),
+ },
+ // Data sources
+ {
+ ConfigVariables: testConfigRoutingTableRouteMin,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ # single routing table route
+ data "stackit_routing_table_route" "route" {
+ organization_id = stackit_routing_table_route.route.organization_id
+ network_area_id = stackit_routing_table_route.route.network_area_id
+ routing_table_id = stackit_routing_table_route.route.routing_table_id
+ route_id = stackit_routing_table_route.route.route_id
+ }
+
+ # all routing table routes in routing table
+ data "stackit_routing_table_routes" "routes" {
+ organization_id = stackit_routing_table_route.route.organization_id
+ network_area_id = stackit_routing_table_route.route.network_area_id
+ routing_table_id = stackit_routing_table_route.route.routing_table_id
+ }
+ `,
+ testutil.IaaSProviderConfig(), resourceRoutingTableRouteMinConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table route
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["network_area_id"])),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "routing_table_id",
+ "data.stackit_routing_table_route.route", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "route_id",
+ "data.stackit_routing_table_route.route", "route_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "region", testutil.Region),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["destination_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["destination_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["next_hop_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["next_hop_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "labels.%", "0"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_route.route", "created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_route.route", "updated_at"),
+
+ // Routing table routes
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["network_area_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "region", testutil.Region),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.#", "1"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "routing_table_id",
+ "data.stackit_routing_table_routes.routes", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "route_id",
+ "data.stackit_routing_table_routes.routes", "routes.0.route_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["destination_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["destination_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["next_hop_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["next_hop_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.labels.%", "0"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_routes.routes", "routes.0.created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_routes.routes", "routes.0.updated_at"),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testConfigRoutingTableRouteMinUpdated,
+ ResourceName: "stackit_routing_table_route.route",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_routing_table_route.route"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_routing_table_route.route")
+ }
+ region, ok := r.Primary.Attributes["region"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute region")
+ }
+ networkAreaId, ok := r.Primary.Attributes["network_area_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute network_area_id")
+ }
+ routingTableId, ok := r.Primary.Attributes["routing_table_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute routing_table_id")
+ }
+ routeId, ok := r.Primary.Attributes["route_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute route_id")
+ }
+ return fmt.Sprintf("%s,%s,%s,%s,%s", testutil.OrganizationId, region, networkAreaId, routingTableId, routeId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testConfigRoutingTableRouteMinUpdated,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMinConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["routing_table_name"])),
+
+ // Routing table route
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "routing_table_id"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "stackit_routing_table_route.route", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["destination_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["destination_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["next_hop_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["next_hop_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "labels.%", "0"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "updated_at"),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+ })
+
+ t.Run("TestAccRoutingTableRouteMax", func(t *testing.T) {
+ t.Logf("TestAccRoutingTableRouteMax")
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testConfigRoutingTableRouteMax,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMaxConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["routing_table_name"])),
+
+ // Routing table route
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "routing_table_id"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "stackit_routing_table_route.route", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["destination_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["destination_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["next_hop_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["next_hop_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "labels.%", "1"),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["label"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "updated_at"),
+ ),
+ },
+ // Data sources
+ {
+ ConfigVariables: testConfigRoutingTableRouteMax,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ # single routing table route
+ data "stackit_routing_table_route" "route" {
+ organization_id = stackit_routing_table_route.route.organization_id
+ network_area_id = stackit_routing_table_route.route.network_area_id
+ routing_table_id = stackit_routing_table_route.route.routing_table_id
+ route_id = stackit_routing_table_route.route.route_id
+ }
+
+ # all routing table routes in routing table
+ data "stackit_routing_table_routes" "routes" {
+ organization_id = stackit_routing_table_route.route.organization_id
+ network_area_id = stackit_routing_table_route.route.network_area_id
+ routing_table_id = stackit_routing_table_route.route.routing_table_id
+ }
+ `,
+ testutil.IaaSProviderConfig(), resourceRoutingTableRouteMaxConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table route
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["network_area_id"])),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "routing_table_id",
+ "data.stackit_routing_table_route.route", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "route_id",
+ "data.stackit_routing_table_route.route", "route_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "region", testutil.Region),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["destination_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["destination_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["next_hop_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["next_hop_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "labels.%", "1"),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_route.route", "labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["label"])),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_route.route", "created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_route.route", "updated_at"),
+
+ // Routing table routes
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["organization_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["network_area_id"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "region", testutil.Region),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.#", "1"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "routing_table_id",
+ "data.stackit_routing_table_routes.routes", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table_route.route", "route_id",
+ "data.stackit_routing_table_routes.routes", "routes.0.route_id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["destination_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["destination_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["next_hop_type"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["next_hop_value"])),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.labels.%", "1"),
+ resource.TestCheckResourceAttr("data.stackit_routing_table_routes.routes", "routes.0.labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["label"])),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_routes.routes", "routes.0.created_at"),
+ resource.TestCheckResourceAttrSet("data.stackit_routing_table_routes.routes", "routes.0.updated_at"),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testConfigRoutingTableRouteMaxUpdated,
+ ResourceName: "stackit_routing_table_route.route",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_routing_table_route.route"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_routing_table_route.route")
+ }
+ region, ok := r.Primary.Attributes["region"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute region")
+ }
+ networkAreaId, ok := r.Primary.Attributes["network_area_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute network_area_id")
+ }
+ routingTableId, ok := r.Primary.Attributes["routing_table_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute routing_table_id")
+ }
+ routeId, ok := r.Primary.Attributes["route_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute route_id")
+ }
+ return fmt.Sprintf("%s,%s,%s,%s,%s", testutil.OrganizationId, region, networkAreaId, routingTableId, routeId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testConfigRoutingTableRouteMaxUpdated,
+ Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMaxConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ // Routing table
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
+ resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["routing_table_name"])),
+
+ // Routing table route
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["organization_id"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "network_area_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["network_area_id"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "routing_table_id"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_routing_table.routing_table", "routing_table_id",
+ "stackit_routing_table_route.route", "routing_table_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["destination_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "destination.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["destination_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.type", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["next_hop_type"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "next_hop.value", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["next_hop_value"])),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "labels.%", "1"),
+ resource.TestCheckResourceAttr("stackit_routing_table_route.route", "labels.acc-test", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["label"])),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "created_at"),
+ resource.TestCheckResourceAttrSet("stackit_routing_table_route.route", "updated_at"),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+ })
+}
+
+func testAccCheckDestroy(s *terraform.State) error {
+ checkFunctions := []func(s *terraform.State) error{
+ testAccCheckRoutingTableDestroy,
+ testAccCheckRoutingTableRouteDestroy,
+ }
+ var errs []error
+
+ wg := sync.WaitGroup{}
+ wg.Add(len(checkFunctions))
+
+ for _, f := range checkFunctions {
+ go func() {
+ err := f(s)
+ if err != nil {
+ errs = append(errs, err)
+ }
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ return errors.Join(errs...)
+}
+
+func testAccCheckRoutingTableDestroy(s *terraform.State) error {
+ ctx := context.Background()
+ var client *iaasalpha.APIClient
+ var err error
+ if testutil.IaaSCustomEndpoint == "" {
+ client, err = iaasalpha.NewAPIClient()
+ } else {
+ client, err = iaasalpha.NewAPIClient(
+ stackitSdkConfig.WithEndpoint(testutil.IaaSCustomEndpoint),
+ )
+ }
+ if err != nil {
+ return fmt.Errorf("creating client: %w", err)
+ }
+
+ var errs []error
+ // routing tables
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "stackit_routing_table" {
+ continue
+ }
+ routingTableId := strings.Split(rs.Primary.ID, core.Separator)[3]
+ region := strings.Split(rs.Primary.ID, core.Separator)[1]
+ err := client.DeleteRoutingTableFromAreaExecute(ctx, testutil.OrganizationId, testNetworkAreaId, region, routingTableId)
+ if err != nil {
+ var oapiErr *oapierror.GenericOpenAPIError
+ if errors.As(err, &oapiErr) {
+ if oapiErr.StatusCode == http.StatusNotFound {
+ continue
+ }
+ }
+ errs = append(errs, fmt.Errorf("cannot trigger routing table deletion %q: %w", routingTableId, err))
+ }
+ }
+
+ return errors.Join(errs...)
+}
+
+func testAccCheckRoutingTableRouteDestroy(s *terraform.State) error {
+ ctx := context.Background()
+ var client *iaasalpha.APIClient
+ var err error
+ if testutil.IaaSCustomEndpoint == "" {
+ client, err = iaasalpha.NewAPIClient()
+ } else {
+ client, err = iaasalpha.NewAPIClient(
+ stackitSdkConfig.WithEndpoint(testutil.IaaSCustomEndpoint),
+ )
+ }
+ if err != nil {
+ return fmt.Errorf("creating client: %w", err)
+ }
+
+ var errs []error
+ // routes
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "stackit_routing_table_route" {
+ continue
+ }
+ routingTableRouteId := strings.Split(rs.Primary.ID, core.Separator)[4]
+ routingTableId := strings.Split(rs.Primary.ID, core.Separator)[3]
+ region := strings.Split(rs.Primary.ID, core.Separator)[1]
+ err := client.DeleteRouteFromRoutingTableExecute(ctx, testutil.OrganizationId, testNetworkAreaId, region, routingTableId, routingTableRouteId)
+ if err != nil {
+ var oapiErr *oapierror.GenericOpenAPIError
+ if errors.As(err, &oapiErr) {
+ if oapiErr.StatusCode == http.StatusNotFound {
+ continue
+ }
+ }
+ errs = append(errs, fmt.Errorf("cannot trigger routing table route deletion %q: %w", routingTableId, err))
+ }
+ }
+
+ return errors.Join(errs...)
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/route/datasource.go b/stackit/internal/services/iaasalpha/routingtable/route/datasource.go
new file mode 100644
index 00000000..51846f69
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/route/datasource.go
@@ -0,0 +1,120 @@
+package route
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ shared "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ iaasalphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &routingTableRouteDataSource{}
+)
+
+// NewRoutingTableRouteDataSource is a helper function to simplify the provider implementation.
+func NewRoutingTableRouteDataSource() datasource.DataSource {
+ return &routingTableRouteDataSource{}
+}
+
+// routingTableRouteDataSource is the data source implementation.
+type routingTableRouteDataSource struct {
+ client *iaasalpha.APIClient
+ providerData core.ProviderData
+}
+
+// Metadata returns the data source type name.
+func (d *routingTableRouteDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_routing_table_route"
+}
+
+func (d *routingTableRouteDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckExperimentEnabled(ctx, &d.providerData, features.RoutingTablesExperiment, "stackit_routing_table_route", core.Datasource, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := iaasalphaUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ d.client = apiClient
+ tflog.Info(ctx, "IaaS client configured")
+}
+
+// Schema defines the schema for the data source.
+func (d *routingTableRouteDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ description := "Routing table route datasource schema. Must have a `region` specified in the provider configuration."
+ resp.Schema = schema.Schema{
+ Description: description,
+ MarkdownDescription: features.AddExperimentDescription(description, features.RoutingTablesExperiment, core.Datasource),
+ Attributes: shared.GetRouteDataSourceAttributes(),
+ }
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (d *routingTableRouteDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model shared.RouteModel
+ diags := req.Config.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ region := d.providerData.GetRegionWithOverride(model.Region)
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ routeId := model.RouteId.ValueString()
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "route_id", routeId)
+
+ routeResp, err := d.client.GetRouteOfRoutingTable(ctx, organizationId, networkAreaId, region, routingTableId, routeId).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, err.Error(), err.Error())
+ utils.LogError(
+ ctx,
+ &resp.Diagnostics,
+ err,
+ "Reading routing table route",
+ fmt.Sprintf("Routing table route with ID %q, routing table with ID %q or network area with ID %q does not exist in organization %q.", routeId, routingTableId, networkAreaId, organizationId),
+ map[int]string{
+ http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", organizationId),
+ },
+ )
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ err = shared.MapRouteModel(ctx, routeResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table route", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table route read")
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/route/resource.go b/stackit/internal/services/iaasalpha/routingtable/route/resource.go
new file mode 100644
index 00000000..d10e569f
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/route/resource.go
@@ -0,0 +1,516 @@
+package route
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "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/planmodifier"
+ "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"
+ sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ iaasalphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &routeResource{}
+ _ resource.ResourceWithConfigure = &routeResource{}
+ _ resource.ResourceWithImportState = &routeResource{}
+)
+
+// NewRoutingTableRouteResource is a helper function to simplify the provider implementation.
+func NewRoutingTableRouteResource() resource.Resource {
+ return &routeResource{}
+}
+
+// routeResource is the resource implementation.
+type routeResource struct {
+ client *iaasalpha.APIClient
+ providerData core.ProviderData
+}
+
+// Metadata returns the resource type name.
+func (r *routeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_routing_table_route"
+}
+
+// Configure adds the provider configured client to the resource.
+func (r *routeResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ var ok bool
+ r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckExperimentEnabled(ctx, &r.providerData, features.RoutingTablesExperiment, "stackit_routing_table_route", core.Resource, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := iaasalphaUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ r.client = apiClient
+ tflog.Info(ctx, "IaaS alpha client configured")
+}
+
+// Schema defines the schema for the resource.
+func (r *routeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ description := "Routing table route resource schema. Must have a `region` specified in the provider configuration."
+ resp.Schema = schema.Schema{
+ Description: description,
+ MarkdownDescription: features.AddExperimentDescription(description, features.RoutingTablesExperiment, core.Resource),
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Terraform's internal resource ID. It is structured as \"`organization_id`,`region`,`network_area_id`,`routing_table_id`,`route_id`\".",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "organization_id": schema.StringAttribute{
+ Description: "STACKIT organization ID to which the routing table is associated.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "routing_table_id": schema.StringAttribute{
+ Description: "The routing tables ID.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "region": schema.StringAttribute{
+ Description: "The resource region. If not defined, the provider region is used.",
+ Optional: true,
+ // must be computed to allow for storing the override value from the provider
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "route_id": schema.StringAttribute{
+ Description: "The ID of the route.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "destination": schema.SingleNestedAttribute{
+ Description: "Destination of the route.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: fmt.Sprintf("CIDRV type. %s %s", utils.FormatPossibleValues("cidrv4", "cidrv6"), "Only `cidrv4` is supported during experimental stage."),
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "value": schema.StringAttribute{
+ Description: "An CIDR string.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ },
+ },
+ "network_area_id": schema.StringAttribute{
+ Description: "The network area ID to which the routing table is associated.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "labels": schema.MapAttribute{
+ Description: "Labels are key-value string pairs which can be attached to a resource container",
+ ElementType: types.StringType,
+ Optional: true,
+ },
+ "next_hop": schema.SingleNestedAttribute{
+ Description: "Next hop destination.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: fmt.Sprintf("%s %s.", utils.FormatPossibleValues("blackhole", "internet", "ipv4", "ipv6"), "Only `cidrv4` is supported during experimental stage."),
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "value": schema.StringAttribute{
+ Description: "Either IPv4 or IPv6 (not set for blackhole and internet). Only IPv4 supported during experimental stage.",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ },
+ },
+ "created_at": schema.StringAttribute{
+ Description: "Date-time when the route was created.",
+ Computed: true,
+ },
+ "updated_at": schema.StringAttribute{
+ Description: "Date-time when the route was updated.",
+ Computed: true,
+ },
+ },
+ }
+}
+
+// Create creates the resource and sets the initial Terraform state.
+func (r *routeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
+ var model shared.RouteModel
+ diags := req.Plan.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "region", region)
+
+ // Create new routing table route
+ payload, err := toCreatePayload(ctx, &model.RouteReadModel)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating routing table route", fmt.Sprintf("Creating API payload: %v", err))
+ return
+ }
+
+ routeResp, err := r.client.AddRoutesToRoutingTable(ctx, organizationId, networkAreaId, region, routingTableId).AddRoutesToRoutingTablePayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating routing table route", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ // Map response body to schema
+ err = mapFieldsFromList(ctx, routeResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating routing table route", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ ctx = tflog.SetField(ctx, "route_id", model.RouteId.ValueString())
+
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table route created")
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (r *routeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model shared.RouteModel
+ diags := req.State.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ organizationId := model.OrganizationId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ routeId := model.RouteId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "route_id", routeId)
+
+ routeResp, err := r.client.GetRouteOfRoutingTable(ctx, organizationId, networkAreaId, region, routingTableId, routeId).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table route", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ // Map response body to schema
+ err = shared.MapRouteModel(ctx, routeResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table route", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+
+ // Set refreshed state
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table route read.")
+}
+
+// Update updates the resource and sets the updated Terraform state on success.
+func (r *routeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
+ // Retrieve values from plan
+ var model shared.RouteModel
+ diags := req.Plan.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ routeId := model.RouteId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "route_id", routeId)
+
+ // Retrieve values from state
+ var stateModel shared.RouteModel
+ diags = req.State.Get(ctx, &stateModel)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Generate API request body from model
+ payload, err := toUpdatePayload(ctx, &model, stateModel.Labels)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating routing table route", fmt.Sprintf("Creating API payload: %v", err))
+ return
+ }
+
+ route, err := r.client.UpdateRouteOfRoutingTable(ctx, organizationId, networkAreaId, region, routingTableId, routeId).UpdateRouteOfRoutingTablePayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating routing table route", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ // Map response body to schema
+ err = shared.MapRouteModel(ctx, route, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating routing table route", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ // Set refreshed state
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table route updated")
+}
+
+// Delete deletes the resource and removes the Terraform state on success.
+func (r *routeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
+ var model shared.RouteModel
+ diags := req.State.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ routeId := model.RouteId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "route_id", routeId)
+ ctx = tflog.SetField(ctx, "region", region)
+
+ // Delete existing routing table route
+ err := r.client.DeleteRouteFromRoutingTable(ctx, organizationId, networkAreaId, region, routingTableId, routeId).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error routing table route", fmt.Sprintf("Calling API: %v", err))
+ }
+
+ tflog.Info(ctx, "Routing table route deleted")
+}
+
+// ImportState imports a resource into the Terraform state on success.
+// The expected format of the routing table route resource import identifier is: organization_id,region,network_area_id,routing_table_id,route_id
+func (r *routeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ idParts := strings.Split(req.ID, core.Separator)
+
+ if len(idParts) != 5 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" || idParts[4] == "" {
+ core.LogAndAddError(ctx, &resp.Diagnostics,
+ "Error importing routing table",
+ fmt.Sprintf("Expected import identifier with format: [organization_id],[region],[network_area_id],[routing_table_id],[route_id] Got: %q", req.ID),
+ )
+ return
+ }
+
+ organizationId := idParts[0]
+ region := idParts[1]
+ networkAreaId := idParts[2]
+ routingTableId := idParts[3]
+ routeId := idParts[4]
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "route_id", routeId)
+
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization_id"), organizationId)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_area_id"), networkAreaId)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("routing_table_id"), routingTableId)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("route_id"), routeId)...)
+ tflog.Info(ctx, "Routing table route state imported")
+}
+
+func mapFieldsFromList(ctx context.Context, routeResp *iaasalpha.RouteListResponse, model *shared.RouteModel, region string) error {
+ if routeResp == nil || routeResp.Items == nil {
+ return fmt.Errorf("response input is nil")
+ } else if len(*routeResp.Items) < 1 {
+ return fmt.Errorf("no routes found in response")
+ } else if len(*routeResp.Items) > 1 {
+ return fmt.Errorf("more than 1 route found in response")
+ }
+
+ route := (*routeResp.Items)[0]
+ return shared.MapRouteModel(ctx, &route, model, region)
+}
+
+func toCreatePayload(ctx context.Context, model *shared.RouteReadModel) (*iaasalpha.AddRoutesToRoutingTablePayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
+ if err != nil {
+ return nil, fmt.Errorf("converting to Go map: %w", err)
+ }
+
+ nextHopPayload, err := toNextHopPayload(ctx, model)
+ if err != nil {
+ return nil, err
+ }
+ destinationPayload, err := toDestinationPayload(ctx, model)
+ if err != nil {
+ return nil, err
+ }
+
+ return &iaasalpha.AddRoutesToRoutingTablePayload{
+ Items: &[]iaasalpha.Route{
+ {
+ Labels: &labels,
+ Nexthop: nextHopPayload,
+ Destination: destinationPayload,
+ },
+ },
+ }, nil
+}
+
+func toUpdatePayload(ctx context.Context, model *shared.RouteModel, currentLabels types.Map) (*iaasalpha.UpdateRouteOfRoutingTablePayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
+ if err != nil {
+ return nil, fmt.Errorf("converting to Go map: %w", err)
+ }
+
+ return &iaasalpha.UpdateRouteOfRoutingTablePayload{
+ Labels: &labels,
+ }, nil
+}
+
+func toNextHopPayload(ctx context.Context, model *shared.RouteReadModel) (*iaasalpha.RouteNexthop, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+ if utils.IsUndefined(model.NextHop) {
+ return nil, nil
+ }
+
+ nexthopModel := shared.RouteNextHop{}
+ diags := model.NextHop.As(ctx, &nexthopModel, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return nil, core.DiagsToError(diags)
+ }
+
+ switch nexthopModel.Type.ValueString() {
+ case "blackhole":
+ return sdkUtils.Ptr(iaasalpha.NexthopBlackholeAsRouteNexthop(iaasalpha.NewNexthopBlackhole("blackhole"))), nil
+ case "internet":
+ return sdkUtils.Ptr(iaasalpha.NexthopInternetAsRouteNexthop(iaasalpha.NewNexthopInternet("internet"))), nil
+ case "ipv4":
+ return sdkUtils.Ptr(iaasalpha.NexthopIPv4AsRouteNexthop(iaasalpha.NewNexthopIPv4("ipv4", nexthopModel.Value.ValueString()))), nil
+ case "ipv6":
+ return sdkUtils.Ptr(iaasalpha.NexthopIPv6AsRouteNexthop(iaasalpha.NewNexthopIPv6("ipv6", nexthopModel.Value.ValueString()))), nil
+ }
+ return nil, fmt.Errorf("unknown nexthop type: %s", nexthopModel.Type.ValueString())
+}
+
+func toDestinationPayload(ctx context.Context, model *shared.RouteReadModel) (*iaasalpha.RouteDestination, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+ if utils.IsUndefined(model.Destination) {
+ return nil, nil
+ }
+
+ destinationModel := shared.RouteDestination{}
+ diags := model.Destination.As(ctx, &destinationModel, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return nil, core.DiagsToError(diags)
+ }
+
+ switch destinationModel.Type.ValueString() {
+ case "cidrv4":
+ return sdkUtils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(iaasalpha.NewDestinationCIDRv4("cidrv4", destinationModel.Value.ValueString()))), nil
+ case "cidrv6":
+ return sdkUtils.Ptr(iaasalpha.DestinationCIDRv6AsRouteDestination(iaasalpha.NewDestinationCIDRv6("cidrv6", destinationModel.Value.ValueString()))), nil
+ }
+ return nil, fmt.Errorf("unknown destination type: %s", destinationModel.Type.ValueString())
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/route/resource_test.go b/stackit/internal/services/iaasalpha/routingtable/route/resource_test.go
new file mode 100644
index 00000000..9d59f855
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/route/resource_test.go
@@ -0,0 +1,452 @@
+package route
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+)
+
+const (
+ testRegion = "eu02"
+)
+
+var (
+ organizationId = uuid.New()
+ networkAreaId = uuid.New()
+ routingTableId = uuid.New()
+ routeId = uuid.New()
+)
+
+func Test_mapFieldsFromList(t *testing.T) {
+ type args struct {
+ routeResp *iaasalpha.RouteListResponse
+ model *shared.RouteModel
+ region string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expectedModel *shared.RouteModel
+ }{
+ {
+ name: "response is nil",
+ args: args{
+ model: &shared.RouteModel{},
+ routeResp: nil,
+ },
+ wantErr: true,
+ },
+ {
+ name: "response items is nil",
+ args: args{
+ model: &shared.RouteModel{},
+ routeResp: &iaasalpha.RouteListResponse{
+ Items: nil,
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ routeResp: &iaasalpha.RouteListResponse{
+ Items: nil,
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "response items is empty",
+ args: args{
+ model: &shared.RouteModel{},
+ routeResp: &iaasalpha.RouteListResponse{
+ Items: &[]iaasalpha.Route{},
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "response items contains more than one route",
+ args: args{
+ model: &shared.RouteModel{},
+ routeResp: &iaasalpha.RouteListResponse{
+ Items: &[]iaasalpha.Route{
+ {
+ Id: utils.Ptr(uuid.NewString()),
+ },
+ {
+ Id: utils.Ptr(uuid.NewString()),
+ },
+ },
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "success",
+ args: args{
+ model: &shared.RouteModel{
+ RouteReadModel: shared.RouteReadModel{
+ RouteId: types.StringNull(),
+ },
+ RoutingTableId: types.StringValue(routingTableId.String()),
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ },
+ routeResp: &iaasalpha.RouteListResponse{
+ Items: &[]iaasalpha.Route{
+ {
+ Id: utils.Ptr(routeId.String()),
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv4("cidrv4", "58.251.236.138/32"),
+ )),
+ Nexthop: utils.Ptr(iaasalpha.NexthopIPv4AsRouteNexthop(
+ iaasalpha.NewNexthopIPv4("ipv4", "10.20.42.2"),
+ )),
+ Labels: &map[string]interface{}{
+ "foo": "bar",
+ },
+ CreatedAt: nil,
+ UpdatedAt: nil,
+ },
+ },
+ },
+ region: testRegion,
+ },
+ wantErr: false,
+ expectedModel: &shared.RouteModel{
+ RouteReadModel: shared.RouteReadModel{
+ RouteId: types.StringValue(routeId.String()),
+ NextHop: types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv4"),
+ "value": types.StringValue("10.20.42.2"),
+ }),
+ Destination: types.ObjectValueMust(shared.RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv4"),
+ "value": types.StringValue("58.251.236.138/32"),
+ }),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "foo": types.StringValue("bar"),
+ }),
+ CreatedAt: types.StringNull(),
+ UpdatedAt: types.StringNull(),
+ },
+ Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s,%s", organizationId.String(), testRegion, networkAreaId.String(), routingTableId.String(), routeId.String())),
+ RoutingTableId: types.StringValue(routingTableId.String()),
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ if err := mapFieldsFromList(ctx, tt.args.routeResp, tt.args.model, tt.args.region); (err != nil) != tt.wantErr {
+ t.Errorf("mapFieldsFromList() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+
+ diff := cmp.Diff(tt.args.model, tt.expectedModel)
+ if diff != "" && !tt.wantErr {
+ t.Fatalf("mapFieldsFromList(): %s", diff)
+ }
+ })
+ }
+}
+
+func Test_toUpdatePayload(t *testing.T) {
+ type args struct {
+ model *shared.RouteModel
+ currentLabels types.Map
+ }
+ tests := []struct {
+ name string
+ args args
+ want *iaasalpha.UpdateRouteOfRoutingTablePayload
+ wantErr bool
+ }{
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ },
+ wantErr: true,
+ },
+ {
+ name: "max",
+ args: args{
+ model: &shared.RouteModel{
+ RouteReadModel: shared.RouteReadModel{
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "foo1": types.StringValue("bar1"),
+ "foo2": types.StringValue("bar2"),
+ }),
+ },
+ },
+ currentLabels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "foo1": types.StringValue("foobar"),
+ "foo3": types.StringValue("bar3"),
+ }),
+ },
+ want: &iaasalpha.UpdateRouteOfRoutingTablePayload{
+ Labels: &map[string]interface{}{
+ "foo1": "bar1",
+ "foo2": "bar2",
+ "foo3": nil,
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ got, err := toUpdatePayload(ctx, tt.args.model, tt.args.currentLabels)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("toUpdatePayload() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ diff := cmp.Diff(got, tt.want)
+ if diff != "" {
+ t.Fatalf("toUpdatePayload(): %s", diff)
+ }
+ })
+ }
+}
+
+func Test_toNextHopPayload(t *testing.T) {
+ type args struct {
+ model *shared.RouteReadModel
+ }
+ tests := []struct {
+ name string
+ args args
+ want *iaasalpha.RouteNexthop
+ wantErr bool
+ }{
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ },
+ wantErr: true,
+ },
+ {
+ name: "ipv4",
+ args: args{
+ model: &shared.RouteReadModel{
+ NextHop: types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv4"),
+ "value": types.StringValue("10.20.42.2"),
+ }),
+ },
+ },
+ wantErr: false,
+ want: utils.Ptr(iaasalpha.NexthopIPv4AsRouteNexthop(
+ iaasalpha.NewNexthopIPv4("ipv4", "10.20.42.2"),
+ )),
+ },
+ {
+ name: "ipv6",
+ args: args{
+ model: &shared.RouteReadModel{
+ NextHop: types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv6"),
+ "value": types.StringValue("172b:f881:46fe:d89a:9332:90f7:3485:236d"),
+ }),
+ },
+ },
+ wantErr: false,
+ want: utils.Ptr(iaasalpha.NexthopIPv6AsRouteNexthop(
+ iaasalpha.NewNexthopIPv6("ipv6", "172b:f881:46fe:d89a:9332:90f7:3485:236d"),
+ )),
+ },
+ {
+ name: "internet",
+ args: args{
+ model: &shared.RouteReadModel{
+ NextHop: types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("internet"),
+ "value": types.StringNull(),
+ }),
+ },
+ },
+ wantErr: false,
+ want: utils.Ptr(iaasalpha.NexthopInternetAsRouteNexthop(
+ iaasalpha.NewNexthopInternet("internet"),
+ )),
+ },
+ {
+ name: "blackhole",
+ args: args{
+ model: &shared.RouteReadModel{
+ NextHop: types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("blackhole"),
+ "value": types.StringNull(),
+ }),
+ },
+ },
+ wantErr: false,
+ want: utils.Ptr(iaasalpha.NexthopBlackholeAsRouteNexthop(
+ iaasalpha.NewNexthopBlackhole("blackhole"),
+ )),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ got, err := toNextHopPayload(ctx, tt.args.model)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("toNextHopPayload() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("toNextHopPayload() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_toDestinationPayload(t *testing.T) {
+ type args struct {
+ model *shared.RouteReadModel
+ }
+ tests := []struct {
+ name string
+ args args
+ want *iaasalpha.RouteDestination
+ wantErr bool
+ }{
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ },
+ wantErr: true,
+ },
+ {
+ name: "cidrv4",
+ args: args{
+ model: &shared.RouteReadModel{
+ Destination: types.ObjectValueMust(shared.RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv4"),
+ "value": types.StringValue("58.251.236.138/32"),
+ }),
+ },
+ },
+ wantErr: false,
+ want: utils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv4("cidrv4", "58.251.236.138/32"),
+ )),
+ },
+ {
+ name: "cidrv6",
+ args: args{
+ model: &shared.RouteReadModel{
+ Destination: types.ObjectValueMust(shared.RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv6"),
+ "value": types.StringValue("2001:0db8:3c4d:1a2b::/64"),
+ }),
+ },
+ },
+ wantErr: false,
+ want: utils.Ptr(iaasalpha.DestinationCIDRv6AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv6("cidrv6", "2001:0db8:3c4d:1a2b::/64"),
+ )),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ got, err := toDestinationPayload(ctx, tt.args.model)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("toDestinationPayload() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("toDestinationPayload() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_toCreatePayload(t *testing.T) {
+ type args struct {
+ model *shared.RouteReadModel
+ }
+ tests := []struct {
+ name string
+ args args
+ want *iaasalpha.AddRoutesToRoutingTablePayload
+ wantErr bool
+ }{
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ },
+ wantErr: true,
+ },
+ {
+ name: "max",
+ args: args{
+ model: &shared.RouteReadModel{
+ NextHop: types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv4"),
+ "value": types.StringValue("10.20.42.2"),
+ }),
+ Destination: types.ObjectValueMust(shared.RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv4"),
+ "value": types.StringValue("58.251.236.138/32"),
+ }),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "foo1": types.StringValue("bar1"),
+ "foo2": types.StringValue("bar2"),
+ }),
+ },
+ },
+ want: &iaasalpha.AddRoutesToRoutingTablePayload{
+ Items: &[]iaasalpha.Route{
+ {
+ Labels: &map[string]interface{}{
+ "foo1": "bar1",
+ "foo2": "bar2",
+ },
+ Nexthop: utils.Ptr(iaasalpha.NexthopIPv4AsRouteNexthop(
+ iaasalpha.NewNexthopIPv4("ipv4", "10.20.42.2"),
+ )),
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv4("cidrv4", "58.251.236.138/32"),
+ )),
+ },
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ got, err := toCreatePayload(ctx, tt.args.model)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("toCreatePayload() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ diff := cmp.Diff(got, tt.want)
+ if diff != "" {
+ t.Fatalf("toCreatePayload(): %s", diff)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/routes/datasource.go b/stackit/internal/services/iaasalpha/routingtable/routes/datasource.go
new file mode 100644
index 00000000..26ea2d8a
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/routes/datasource.go
@@ -0,0 +1,183 @@
+package routes
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "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"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ shared "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+ iaasalphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &routingTableRoutesDataSource{}
+)
+
+type RoutingTableRoutesDataSourceModel struct {
+ Id types.String `tfsdk:"id"` // needed by TF
+ OrganizationId types.String `tfsdk:"organization_id"`
+ NetworkAreaId types.String `tfsdk:"network_area_id"`
+ RoutingTableId types.String `tfsdk:"routing_table_id"`
+ Region types.String `tfsdk:"region"`
+ Routes types.List `tfsdk:"routes"`
+}
+
+// NewRoutingTableRoutesDataSource is a helper function to simplify the provider implementation.
+func NewRoutingTableRoutesDataSource() datasource.DataSource {
+ return &routingTableRoutesDataSource{}
+}
+
+// routingTableDataSource is the data source implementation.
+type routingTableRoutesDataSource struct {
+ client *iaasalpha.APIClient
+ providerData core.ProviderData
+}
+
+// Metadata returns the data source type name.
+func (d *routingTableRoutesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_routing_table_routes"
+}
+
+func (d *routingTableRoutesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckExperimentEnabled(ctx, &d.providerData, features.RoutingTablesExperiment, "stackit_routing_table_routes", core.Datasource, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := iaasalphaUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ d.client = apiClient
+ tflog.Info(ctx, "IaaS client configured")
+}
+
+// Schema defines the schema for the data source.
+func (d *routingTableRoutesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ description := "Routing table routes datasource schema. Must have a `region` specified in the provider configuration."
+ resp.Schema = schema.Schema{
+ Description: description,
+ MarkdownDescription: features.AddExperimentDescription(description, features.RoutingTablesExperiment, core.Datasource),
+ Attributes: shared.GetRoutesDataSourceAttributes(),
+ }
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (d *routingTableRoutesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model RoutingTableRoutesDataSourceModel
+ diags := req.Config.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ region := d.providerData.GetRegionWithOverride(model.Region)
+ networkAreaId := model.NetworkAreaId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+
+ routesResp, err := d.client.ListRoutesOfRoutingTable(ctx, organizationId, networkAreaId, region, routingTableId).Execute()
+ if err != nil {
+ utils.LogError(
+ ctx,
+ &resp.Diagnostics,
+ err,
+ "Reading routes of routing table",
+ fmt.Sprintf("Routing table with ID %q in network area with ID %q does not exist in organization %q.", routingTableId, networkAreaId, organizationId),
+ map[int]string{
+ http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", organizationId),
+ },
+ )
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ err = mapDataSourceRoutingTableRoutes(ctx, routesResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table routes", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table routes read")
+}
+
+func mapDataSourceRoutingTableRoutes(ctx context.Context, routes *iaasalpha.RouteListResponse, model *RoutingTableRoutesDataSourceModel, region string) error {
+ if routes == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+ if routes.Items == nil {
+ return fmt.Errorf("items input is nil")
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+
+ idParts := []string{organizationId, region, networkAreaId, routingTableId}
+ model.Id = types.StringValue(
+ strings.Join(idParts, core.Separator),
+ )
+
+ itemsList := []attr.Value{}
+ for i, route := range *routes.Items {
+ var routeModel shared.RouteReadModel
+ err := shared.MapRouteReadModel(ctx, &route, &routeModel)
+ if err != nil {
+ return fmt.Errorf("mapping route: %w", err)
+ }
+
+ routeMap := map[string]attr.Value{
+ "route_id": routeModel.RouteId,
+ "destination": routeModel.Destination,
+ "next_hop": routeModel.NextHop,
+ "labels": routeModel.Labels,
+ "created_at": routeModel.CreatedAt,
+ "updated_at": routeModel.UpdatedAt,
+ }
+
+ routeTF, diags := types.ObjectValue(shared.RouteReadModelTypes(), routeMap)
+ if diags.HasError() {
+ return fmt.Errorf("mapping index %d: %w", i, core.DiagsToError(diags))
+ }
+ itemsList = append(itemsList, routeTF)
+ }
+
+ routesListTF, diags := types.ListValue(types.ObjectType{AttrTypes: shared.RouteReadModelTypes()}, itemsList)
+ if diags.HasError() {
+ return core.DiagsToError(diags)
+ }
+
+ model.Region = types.StringValue(region)
+ model.Routes = routesListTF
+
+ return nil
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/routes/datasource_test.go b/stackit/internal/services/iaasalpha/routingtable/routes/datasource_test.go
new file mode 100644
index 00000000..171abd65
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/routes/datasource_test.go
@@ -0,0 +1,199 @@
+package routes
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+)
+
+const (
+ testRegion = "eu02"
+)
+
+var (
+ testOrganizationId = uuid.NewString()
+ testNetworkAreaId = uuid.NewString()
+ testRoutingTableId = uuid.NewString()
+ testRouteId1 = uuid.NewString()
+ testRouteId2 = uuid.NewString()
+)
+
+func Test_mapDataSourceRoutingTableRoutes(t *testing.T) {
+ type args struct {
+ routes *iaasalpha.RouteListResponse
+ model *RoutingTableRoutesDataSourceModel
+ region string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expectedModel *RoutingTableRoutesDataSourceModel
+ }{
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ routes: &iaasalpha.RouteListResponse{
+ Items: &[]iaasalpha.Route{},
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "response is nil",
+ args: args{
+ model: &RoutingTableRoutesDataSourceModel{},
+ routes: nil,
+ },
+ wantErr: true,
+ },
+ {
+ name: "response items is nil",
+ args: args{
+ model: nil,
+ routes: &iaasalpha.RouteListResponse{
+ Items: nil,
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "response items is empty",
+ args: args{
+ model: &RoutingTableRoutesDataSourceModel{
+ OrganizationId: types.StringValue(testOrganizationId),
+ NetworkAreaId: types.StringValue(testNetworkAreaId),
+ RoutingTableId: types.StringValue(testRoutingTableId),
+ Region: types.StringValue(testRegion),
+ },
+ routes: &iaasalpha.RouteListResponse{
+ Items: &[]iaasalpha.Route{},
+ },
+ region: testRegion,
+ },
+ wantErr: false,
+ expectedModel: &RoutingTableRoutesDataSourceModel{
+ Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testOrganizationId, testRegion, testNetworkAreaId, testRoutingTableId)),
+ OrganizationId: types.StringValue(testOrganizationId),
+ NetworkAreaId: types.StringValue(testNetworkAreaId),
+ RoutingTableId: types.StringValue(testRoutingTableId),
+ Region: types.StringValue(testRegion),
+ Routes: types.ListValueMust(
+ types.ObjectType{AttrTypes: shared.RouteReadModelTypes()}, []attr.Value{},
+ ),
+ },
+ },
+ {
+ name: "response items has items",
+ args: args{
+ model: &RoutingTableRoutesDataSourceModel{
+ OrganizationId: types.StringValue(testOrganizationId),
+ NetworkAreaId: types.StringValue(testNetworkAreaId),
+ RoutingTableId: types.StringValue(testRoutingTableId),
+ Region: types.StringValue(testRegion),
+ },
+ routes: &iaasalpha.RouteListResponse{
+ Items: &[]iaasalpha.Route{
+ {
+ Id: utils.Ptr(testRouteId1),
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv4("cidrv4", "58.251.236.138/32"),
+ )),
+ Nexthop: utils.Ptr(iaasalpha.NexthopIPv4AsRouteNexthop(
+ iaasalpha.NewNexthopIPv4("ipv4", "10.20.42.2"),
+ )),
+ Labels: &map[string]interface{}{
+ "foo": "bar",
+ },
+ CreatedAt: nil,
+ UpdatedAt: nil,
+ },
+ {
+ Id: utils.Ptr(testRouteId2),
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv6AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv6("cidrv6", "2001:0db8:3c4d:1a2b::/64"),
+ )),
+ Nexthop: utils.Ptr(iaasalpha.NexthopIPv6AsRouteNexthop(
+ iaasalpha.NewNexthopIPv6("ipv6", "172b:f881:46fe:d89a:9332:90f7:3485:236d"),
+ )),
+ Labels: &map[string]interface{}{
+ "key": "value",
+ },
+ CreatedAt: nil,
+ UpdatedAt: nil,
+ },
+ },
+ },
+ region: testRegion,
+ },
+ wantErr: false,
+ expectedModel: &RoutingTableRoutesDataSourceModel{
+ Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testOrganizationId, testRegion, testNetworkAreaId, testRoutingTableId)),
+ OrganizationId: types.StringValue(testOrganizationId),
+ NetworkAreaId: types.StringValue(testNetworkAreaId),
+ RoutingTableId: types.StringValue(testRoutingTableId),
+ Region: types.StringValue(testRegion),
+ Routes: types.ListValueMust(
+ types.ObjectType{AttrTypes: shared.RouteReadModelTypes()}, []attr.Value{
+ types.ObjectValueMust(shared.RouteReadModelTypes(), map[string]attr.Value{
+ "route_id": types.StringValue(testRouteId1),
+ "created_at": types.StringNull(),
+ "updated_at": types.StringNull(),
+ "labels": types.MapValueMust(types.StringType, map[string]attr.Value{
+ "foo": types.StringValue("bar"),
+ }),
+ "destination": types.ObjectValueMust(shared.RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv4"),
+ "value": types.StringValue("58.251.236.138/32"),
+ }),
+ "next_hop": types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv4"),
+ "value": types.StringValue("10.20.42.2"),
+ }),
+ }),
+ types.ObjectValueMust(shared.RouteReadModelTypes(), map[string]attr.Value{
+ "route_id": types.StringValue(testRouteId2),
+ "created_at": types.StringNull(),
+ "updated_at": types.StringNull(),
+ "labels": types.MapValueMust(types.StringType, map[string]attr.Value{
+ "key": types.StringValue("value"),
+ }),
+ "destination": types.ObjectValueMust(shared.RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv6"),
+ "value": types.StringValue("2001:0db8:3c4d:1a2b::/64"),
+ }),
+ "next_hop": types.ObjectValueMust(shared.RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv6"),
+ "value": types.StringValue("172b:f881:46fe:d89a:9332:90f7:3485:236d"),
+ }),
+ }),
+ },
+ ),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ if err := mapDataSourceRoutingTableRoutes(ctx, tt.args.routes, tt.args.model, tt.args.region); (err != nil) != tt.wantErr {
+ t.Errorf("mapDataSourceRoutingTableRoutes() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+
+ diff := cmp.Diff(tt.args.model, tt.expectedModel)
+ if diff != "" && !tt.wantErr {
+ t.Fatalf("mapFieldsFromList(): %s", diff)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/shared/route.go b/stackit/internal/services/iaasalpha/routingtable/shared/route.go
new file mode 100644
index 00000000..e05cf78d
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/shared/route.go
@@ -0,0 +1,213 @@
+package shared
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
+)
+
+type RouteReadModel struct {
+ RouteId types.String `tfsdk:"route_id"`
+ Destination types.Object `tfsdk:"destination"`
+ NextHop types.Object `tfsdk:"next_hop"`
+ Labels types.Map `tfsdk:"labels"`
+ CreatedAt types.String `tfsdk:"created_at"`
+ UpdatedAt types.String `tfsdk:"updated_at"`
+}
+
+func RouteReadModelTypes() map[string]attr.Type {
+ return map[string]attr.Type{
+ "route_id": types.StringType,
+ "destination": types.ObjectType{AttrTypes: RouteDestinationTypes},
+ "next_hop": types.ObjectType{AttrTypes: RouteNextHopTypes},
+ "labels": types.MapType{ElemType: types.StringType},
+ "created_at": types.StringType,
+ "updated_at": types.StringType,
+ }
+}
+
+type RouteModel struct {
+ RouteReadModel
+ Id types.String `tfsdk:"id"` // needed by TF
+ OrganizationId types.String `tfsdk:"organization_id"`
+ RoutingTableId types.String `tfsdk:"routing_table_id"`
+ NetworkAreaId types.String `tfsdk:"network_area_id"`
+ Region types.String `tfsdk:"region"`
+}
+
+func RouteModelTypes() map[string]attr.Type {
+ modelTypes := RouteReadModelTypes()
+ modelTypes["id"] = types.StringType
+ modelTypes["organization_id"] = types.StringType
+ modelTypes["routing_table_id"] = types.StringType
+ modelTypes["network_area_id"] = types.StringType
+ modelTypes["region"] = types.StringType
+ return modelTypes
+}
+
+// RouteDestination is the struct corresponding to RouteReadModel.Destination
+type RouteDestination struct {
+ Type types.String `tfsdk:"type"`
+ Value types.String `tfsdk:"value"`
+}
+
+// RouteDestinationTypes Types corresponding to routeDestination
+var RouteDestinationTypes = map[string]attr.Type{
+ "type": types.StringType,
+ "value": types.StringType,
+}
+
+// RouteNextHop is the struct corresponding to RouteReadModel.NextHop
+type RouteNextHop struct {
+ Type types.String `tfsdk:"type"`
+ Value types.String `tfsdk:"value"`
+}
+
+// RouteNextHopTypes Types corresponding to routeNextHop
+var RouteNextHopTypes = map[string]attr.Type{
+ "type": types.StringType,
+ "value": types.StringType,
+}
+
+func MapRouteModel(ctx context.Context, route *iaasalpha.Route, model *RouteModel, region string) error {
+ if route == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ err := MapRouteReadModel(ctx, route, &model.RouteReadModel)
+ if err != nil {
+ return err
+ }
+
+ idParts := []string{
+ model.OrganizationId.ValueString(),
+ region,
+ model.NetworkAreaId.ValueString(),
+ model.RoutingTableId.ValueString(),
+ model.RouteId.ValueString(),
+ }
+ model.Id = types.StringValue(
+ strings.Join(idParts, core.Separator),
+ )
+ model.Region = types.StringValue(region)
+
+ return nil
+}
+
+func MapRouteReadModel(ctx context.Context, route *iaasalpha.Route, model *RouteReadModel) error {
+ if route == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ var routeId string
+ if model.RouteId.ValueString() != "" {
+ routeId = model.RouteId.ValueString()
+ } else if route.Id != nil {
+ routeId = *route.Id
+ } else {
+ return fmt.Errorf("routing table route id not present")
+ }
+
+ labels, err := iaasUtils.MapLabels(ctx, route.Labels, model.Labels)
+ if err != nil {
+ return err
+ }
+
+ // created at and updated at
+ createdAtTF, updatedAtTF := types.StringNull(), types.StringNull()
+ if route.CreatedAt != nil {
+ createdAtValue := *route.CreatedAt
+ createdAtTF = types.StringValue(createdAtValue.Format(time.RFC3339))
+ }
+ if route.UpdatedAt != nil {
+ updatedAtValue := *route.UpdatedAt
+ updatedAtTF = types.StringValue(updatedAtValue.Format(time.RFC3339))
+ }
+
+ // destination
+ model.Destination, err = MapRouteDestination(route)
+ if err != nil {
+ return fmt.Errorf("error mapping route destination: %w", err)
+ }
+
+ // next hop
+ model.NextHop, err = MapRouteNextHop(route)
+ if err != nil {
+ return fmt.Errorf("error mapping route next hop: %w", err)
+ }
+
+ model.RouteId = types.StringValue(routeId)
+ model.CreatedAt = createdAtTF
+ model.UpdatedAt = updatedAtTF
+ model.Labels = labels
+ return nil
+}
+
+func MapRouteNextHop(routeResp *iaasalpha.Route) (types.Object, error) {
+ if routeResp.Nexthop == nil {
+ return types.ObjectNull(RouteNextHopTypes), nil
+ }
+
+ nextHopMap := map[string]attr.Value{}
+ switch i := routeResp.Nexthop.GetActualInstance().(type) {
+ case *iaasalpha.NexthopIPv4:
+ nextHopMap["type"] = types.StringValue(*i.Type)
+ nextHopMap["value"] = types.StringPointerValue(i.Value)
+ case *iaasalpha.NexthopIPv6:
+ nextHopMap["type"] = types.StringValue(*i.Type)
+ nextHopMap["value"] = types.StringPointerValue(i.Value)
+ case *iaasalpha.NexthopBlackhole:
+ nextHopMap["type"] = types.StringValue(*i.Type)
+ nextHopMap["value"] = types.StringNull()
+ case *iaasalpha.NexthopInternet:
+ nextHopMap["type"] = types.StringValue(*i.Type)
+ nextHopMap["value"] = types.StringNull()
+ default:
+ return types.ObjectNull(RouteNextHopTypes), fmt.Errorf("unexpected Nexthop type: %T", i)
+ }
+
+ nextHopTF, diags := types.ObjectValue(RouteNextHopTypes, nextHopMap)
+ if diags.HasError() {
+ return types.ObjectNull(RouteNextHopTypes), core.DiagsToError(diags)
+ }
+
+ return nextHopTF, nil
+}
+
+func MapRouteDestination(routeResp *iaasalpha.Route) (types.Object, error) {
+ if routeResp.Destination == nil {
+ return types.ObjectNull(RouteDestinationTypes), nil
+ }
+
+ destinationMap := map[string]attr.Value{}
+ switch i := routeResp.Destination.GetActualInstance().(type) {
+ case *iaasalpha.DestinationCIDRv4:
+ destinationMap["type"] = types.StringValue(*i.Type)
+ destinationMap["value"] = types.StringPointerValue(i.Value)
+ case *iaasalpha.DestinationCIDRv6:
+ destinationMap["type"] = types.StringValue(*i.Type)
+ destinationMap["value"] = types.StringPointerValue(i.Value)
+ default:
+ return types.ObjectNull(RouteDestinationTypes), fmt.Errorf("unexpected Destionation type: %T", i)
+ }
+
+ destinationTF, diags := types.ObjectValue(RouteDestinationTypes, destinationMap)
+ if diags.HasError() {
+ return types.ObjectNull(RouteDestinationTypes), core.DiagsToError(diags)
+ }
+
+ return destinationTF, nil
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/shared/route_test.go b/stackit/internal/services/iaasalpha/routingtable/shared/route_test.go
new file mode 100644
index 00000000..6997ad22
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/shared/route_test.go
@@ -0,0 +1,310 @@
+package shared
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+)
+
+const (
+ testRegion = "eu02"
+)
+
+var (
+ testRouteId = uuid.New()
+ testOrganizationId = uuid.New()
+ testNetworkAreaId = uuid.New()
+ testRoutingTableId = uuid.New()
+)
+
+func Test_MapRouteNextHop(t *testing.T) {
+ type args struct {
+ routeResp *iaasalpha.Route
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expected types.Object
+ }{
+ {
+ name: "nexthop is nil",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Nexthop: nil,
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectNull(RouteNextHopTypes),
+ },
+ {
+ name: "nexthop is empty",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Nexthop: &iaasalpha.RouteNexthop{},
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "nexthop ipv4",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Nexthop: utils.Ptr(iaasalpha.NexthopIPv4AsRouteNexthop(
+ iaasalpha.NewNexthopIPv4("ipv4", "10.20.42.2"),
+ )),
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectValueMust(RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv4"),
+ "value": types.StringValue("10.20.42.2"),
+ }),
+ },
+ {
+ name: "nexthop ipv6",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Nexthop: utils.Ptr(iaasalpha.NexthopIPv6AsRouteNexthop(
+ iaasalpha.NewNexthopIPv6("ipv6", "172b:f881:46fe:d89a:9332:90f7:3485:236d"),
+ )),
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectValueMust(RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv6"),
+ "value": types.StringValue("172b:f881:46fe:d89a:9332:90f7:3485:236d"),
+ }),
+ },
+ {
+ name: "nexthop internet",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Nexthop: utils.Ptr(iaasalpha.NexthopInternetAsRouteNexthop(
+ iaasalpha.NewNexthopInternet("internet"),
+ )),
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectValueMust(RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("internet"),
+ "value": types.StringNull(),
+ }),
+ },
+ {
+ name: "nexthop blackhole",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Nexthop: utils.Ptr(iaasalpha.NexthopBlackholeAsRouteNexthop(
+ iaasalpha.NewNexthopBlackhole("blackhole"),
+ )),
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectValueMust(RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("blackhole"),
+ "value": types.StringNull(),
+ }),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ actual, err := MapRouteNextHop(tt.args.routeResp)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("mapNextHop() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ diff := cmp.Diff(actual, tt.expected)
+ if !tt.wantErr && diff != "" {
+ t.Errorf("mapNextHop() result does not match: %s", diff)
+ }
+ })
+ }
+}
+
+func Test_MapRouteDestination(t *testing.T) {
+ type args struct {
+ routeResp *iaasalpha.Route
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expected types.Object
+ }{
+
+ {
+ name: "destination is nil",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Destination: nil,
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectNull(RouteDestinationTypes),
+ },
+ {
+ name: "destination is empty",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Destination: &iaasalpha.RouteDestination{},
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "destination cidrv4",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv4("cidrv4", "58.251.236.138/32"),
+ )),
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectValueMust(RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv4"),
+ "value": types.StringValue("58.251.236.138/32"),
+ }),
+ },
+ {
+ name: "destination cidrv6",
+ args: args{
+ routeResp: &iaasalpha.Route{
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv6AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv6("cidrv6", "2001:0db8:3c4d:1a2b::/64"),
+ )),
+ },
+ },
+ wantErr: false,
+ expected: types.ObjectValueMust(RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv6"),
+ "value": types.StringValue("2001:0db8:3c4d:1a2b::/64"),
+ }),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ actual, err := MapRouteDestination(tt.args.routeResp)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("mapDestination() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ diff := cmp.Diff(actual, tt.expected)
+ if !tt.wantErr && diff != "" {
+ t.Errorf("mapDestination() result does not match: %s", diff)
+ }
+ })
+ }
+}
+
+func TestMapRouteModel(t *testing.T) {
+ createdAt := time.Now()
+ updatedAt := time.Now().Add(5 * time.Minute)
+
+ type args struct {
+ route *iaasalpha.Route
+ model *RouteModel
+ region string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expectedModel *RouteModel
+ }{
+ {
+ name: "route is nil",
+ args: args{
+ model: &RouteModel{},
+ route: nil,
+ region: testRegion,
+ },
+ wantErr: true,
+ },
+ {
+ name: "model is nil",
+ args: args{
+ model: nil,
+ route: &iaasalpha.Route{},
+ region: testRegion,
+ },
+ wantErr: true,
+ },
+ {
+ name: "max",
+ args: args{
+ model: &RouteModel{
+ // state
+ OrganizationId: types.StringValue(testOrganizationId.String()),
+ NetworkAreaId: types.StringValue(testNetworkAreaId.String()),
+ RoutingTableId: types.StringValue(testRoutingTableId.String()),
+ },
+ route: &iaasalpha.Route{
+ Id: utils.Ptr(testRouteId.String()),
+ Destination: utils.Ptr(iaasalpha.DestinationCIDRv4AsRouteDestination(
+ iaasalpha.NewDestinationCIDRv4("cidrv4", "58.251.236.138/32"),
+ )),
+ Labels: &map[string]interface{}{
+ "foo1": "bar1",
+ "foo2": "bar2",
+ },
+ Nexthop: utils.Ptr(
+ iaasalpha.NexthopIPv4AsRouteNexthop(iaasalpha.NewNexthopIPv4("ipv4", "10.20.42.2")),
+ ),
+ CreatedAt: &createdAt,
+ UpdatedAt: &updatedAt,
+ },
+ region: testRegion,
+ },
+ wantErr: false,
+ expectedModel: &RouteModel{
+ Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s,%s",
+ testOrganizationId.String(), testRegion, testNetworkAreaId.String(), testRoutingTableId.String(), testRouteId.String()),
+ ),
+ OrganizationId: types.StringValue(testOrganizationId.String()),
+ NetworkAreaId: types.StringValue(testNetworkAreaId.String()),
+ RoutingTableId: types.StringValue(testRoutingTableId.String()),
+ RouteReadModel: RouteReadModel{
+ RouteId: types.StringValue(testRouteId.String()),
+ Destination: types.ObjectValueMust(RouteDestinationTypes, map[string]attr.Value{
+ "type": types.StringValue("cidrv4"),
+ "value": types.StringValue("58.251.236.138/32"),
+ }),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "foo1": types.StringValue("bar1"),
+ "foo2": types.StringValue("bar2"),
+ }),
+ NextHop: types.ObjectValueMust(RouteNextHopTypes, map[string]attr.Value{
+ "type": types.StringValue("ipv4"),
+ "value": types.StringValue("10.20.42.2"),
+ }),
+ CreatedAt: types.StringValue(createdAt.Format(time.RFC3339)),
+ UpdatedAt: types.StringValue(updatedAt.Format(time.RFC3339)),
+ },
+ Region: types.StringValue(testRegion),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ if err := MapRouteModel(ctx, tt.args.route, tt.args.model, tt.args.region); (err != nil) != tt.wantErr {
+ t.Errorf("MapRouteModel() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ diff := cmp.Diff(tt.args.model, tt.expectedModel)
+ if !tt.wantErr && diff != "" {
+ t.Errorf("MapRouteModel() model does not match: %s", diff)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/shared/shared.go b/stackit/internal/services/iaasalpha/routingtable/shared/shared.go
new file mode 100644
index 00000000..ee212e86
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/shared/shared.go
@@ -0,0 +1,263 @@
+package shared
+
+import (
+ "context"
+ "fmt"
+ "maps"
+ "time"
+
+ iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+type RoutingTableReadModel struct {
+ RoutingTableId types.String `tfsdk:"routing_table_id"`
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ Labels types.Map `tfsdk:"labels"`
+ CreatedAt types.String `tfsdk:"created_at"`
+ UpdatedAt types.String `tfsdk:"updated_at"`
+ Default types.Bool `tfsdk:"default"`
+ SystemRoutes types.Bool `tfsdk:"system_routes"`
+}
+
+func RoutingTableReadModelTypes() map[string]attr.Type {
+ return map[string]attr.Type{
+ "routing_table_id": types.StringType,
+ "name": types.StringType,
+ "description": types.StringType,
+ "labels": types.MapType{ElemType: types.StringType},
+ "created_at": types.StringType,
+ "updated_at": types.StringType,
+ "default": types.BoolType,
+ "system_routes": types.BoolType,
+ }
+}
+
+type RoutingTableDataSourceModel struct {
+ RoutingTableReadModel
+ Id types.String `tfsdk:"id"` // needed by TF
+ OrganizationId types.String `tfsdk:"organization_id"`
+ NetworkAreaId types.String `tfsdk:"network_area_id"`
+ Region types.String `tfsdk:"region"`
+}
+
+func GetDatasourceGetAttributes() map[string]schema.Attribute {
+ // combine the schemas
+ getAttributes := RoutingTableResponseAttributes()
+ maps.Copy(getAttributes, datasourceGetAttributes())
+ getAttributes["id"] = schema.StringAttribute{
+ Description: "Terraform's internal datasource ID. It is structured as \"`organization_id`,`region`,`network_area_id`,`routing_table_id`\".",
+ Computed: true,
+ }
+ return getAttributes
+}
+
+func GetRouteDataSourceAttributes() map[string]schema.Attribute {
+ getAttributes := datasourceGetAttributes()
+ maps.Copy(getAttributes, RouteResponseAttributes())
+ getAttributes["route_id"] = schema.StringAttribute{
+ Description: "Route ID.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ }
+ getAttributes["id"] = schema.StringAttribute{
+ Description: "Terraform's internal datasource ID. It is structured as \"`organization_id`,`region`,`network_area_id`,`routing_table_id`,`route_id`\".",
+ Computed: true,
+ }
+ return getAttributes
+}
+
+func GetRoutesDataSourceAttributes() map[string]schema.Attribute {
+ getAttributes := datasourceGetAttributes()
+ getAttributes["id"] = schema.StringAttribute{
+ Description: "Terraform's internal datasource ID. It is structured as \"`organization_id`,`region`,`network_area_id`,`routing_table_id`,`route_id`\".",
+ Computed: true,
+ }
+ getAttributes["routes"] = schema.ListNestedAttribute{
+ Description: "List of routes.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: RouteResponseAttributes(),
+ },
+ }
+ getAttributes["region"] = schema.StringAttribute{
+ Description: "The datasource region. If not defined, the provider region is used.",
+ Optional: true,
+ }
+ return getAttributes
+}
+
+func datasourceGetAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "organization_id": schema.StringAttribute{
+ Description: "STACKIT organization ID to which the routing table is associated.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "routing_table_id": schema.StringAttribute{
+ Description: "The routing tables ID.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "network_area_id": schema.StringAttribute{
+ Description: "The network area ID to which the routing table is associated.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "region": schema.StringAttribute{
+ Description: "The resource region. If not defined, the provider region is used.",
+ Optional: true,
+ },
+ }
+}
+
+func RouteResponseAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "route_id": schema.StringAttribute{
+ Description: "Route ID.",
+ Computed: true,
+ },
+ "destination": schema.SingleNestedAttribute{
+ Description: "Destination of the route.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: fmt.Sprintf("CIDRV type. %s %s", utils.FormatPossibleValues("cidrv4", "cidrv6"), "Only `cidrv4` is supported during experimental stage."),
+ Computed: true,
+ },
+ "value": schema.StringAttribute{
+ Description: "An CIDR string.",
+ Computed: true,
+ },
+ },
+ },
+ "next_hop": schema.SingleNestedAttribute{
+ Description: "Next hop destination.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: fmt.Sprintf("%s %s.", utils.FormatPossibleValues("blackhole", "internet", "ipv4", "ipv6"), "Only `cidrv4` is supported during experimental stage."),
+ Computed: true,
+ },
+ "value": schema.StringAttribute{
+ Description: "Either IPv4 or IPv6 (not set for blackhole and internet). Only IPv4 supported during experimental stage.",
+ Computed: true,
+ },
+ },
+ },
+ "labels": schema.MapAttribute{
+ Description: "Labels are key-value string pairs which can be attached to a resource container",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ "created_at": schema.StringAttribute{
+ Description: "Date-time when the route was created",
+ Computed: true,
+ },
+ "updated_at": schema.StringAttribute{
+ Description: "Date-time when the route was updated",
+ Computed: true,
+ },
+ }
+}
+
+func RoutingTableResponseAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "routing_table_id": schema.StringAttribute{
+ Description: "The routing tables ID.",
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: "The name of the routing table.",
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "Description of the routing table.",
+ Computed: true,
+ },
+ "labels": schema.MapAttribute{
+ Description: "Labels are key-value string pairs which can be attached to a resource container",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ "default": schema.BoolAttribute{
+ Description: "When true this is the default routing table for this network area. It can't be deleted and is used if the user does not specify it otherwise.",
+ Computed: true,
+ },
+ "system_routes": schema.BoolAttribute{
+ Computed: true,
+ },
+ "created_at": schema.StringAttribute{
+ Description: "Date-time when the routing table was created",
+ Computed: true,
+ },
+ "updated_at": schema.StringAttribute{
+ Description: "Date-time when the routing table was updated",
+ Computed: true,
+ },
+ }
+}
+
+func MapRoutingTableReadModel(ctx context.Context, routingTable *iaasalpha.RoutingTable, model *RoutingTableReadModel) error {
+ if routingTable == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ var routingTableId string
+ if model.RoutingTableId.ValueString() != "" {
+ routingTableId = model.RoutingTableId.ValueString()
+ } else if routingTable.Id != nil {
+ routingTableId = *routingTable.Id
+ } else {
+ return fmt.Errorf("routing table id not present")
+ }
+
+ labels, err := iaasUtils.MapLabels(ctx, routingTable.Labels, model.Labels)
+ if err != nil {
+ return err
+ }
+
+ // created at and updated at
+ createdAtTF, updatedAtTF := types.StringNull(), types.StringNull()
+ if routingTable.CreatedAt != nil {
+ createdAtValue := *routingTable.CreatedAt
+ createdAtTF = types.StringValue(createdAtValue.Format(time.RFC3339))
+ }
+ if routingTable.UpdatedAt != nil {
+ updatedAtValue := *routingTable.UpdatedAt
+ updatedAtTF = types.StringValue(updatedAtValue.Format(time.RFC3339))
+ }
+
+ model.RoutingTableId = types.StringValue(routingTableId)
+ model.Name = types.StringPointerValue(routingTable.Name)
+ model.Description = types.StringPointerValue(routingTable.Description)
+ model.Default = types.BoolPointerValue(routingTable.Default)
+ model.SystemRoutes = types.BoolPointerValue(routingTable.SystemRoutes)
+ model.Labels = labels
+ model.CreatedAt = createdAtTF
+ model.UpdatedAt = updatedAtTF
+ return nil
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/table/datasource.go b/stackit/internal/services/iaasalpha/routingtable/table/datasource.go
new file mode 100644
index 00000000..61b4ddbe
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/table/datasource.go
@@ -0,0 +1,156 @@
+package table
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ iaasalphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &routingTableDataSource{}
+)
+
+// NewRoutingTableDataSource is a helper function to simplify the provider implementation.
+func NewRoutingTableDataSource() datasource.DataSource {
+ return &routingTableDataSource{}
+}
+
+// routingTableDataSource is the data source implementation.
+type routingTableDataSource struct {
+ client *iaasalpha.APIClient
+ providerData core.ProviderData
+}
+
+// Metadata returns the data source type name.
+func (d *routingTableDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_routing_table"
+}
+
+func (d *routingTableDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckExperimentEnabled(ctx, &d.providerData, features.RoutingTablesExperiment, "stackit_routing_table", core.Datasource, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := iaasalphaUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ d.client = apiClient
+ tflog.Info(ctx, "IaaS client configured")
+}
+
+// Schema defines the schema for the data source.
+func (d *routingTableDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ description := "Routing table datasource schema. Must have a `region` specified in the provider configuration."
+ resp.Schema = schema.Schema{
+ Description: description,
+ MarkdownDescription: features.AddExperimentDescription(description, features.RoutingTablesExperiment, core.Datasource),
+ Attributes: shared.GetDatasourceGetAttributes(),
+ }
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (d *routingTableDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model shared.RoutingTableDataSourceModel
+ diags := req.Config.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ region := d.providerData.GetRegionWithOverride(model.Region)
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+
+ routingTableResp, err := d.client.GetRoutingTableOfArea(ctx, organizationId, networkAreaId, region, routingTableId).Execute()
+ if err != nil {
+ utils.LogError(
+ ctx,
+ &resp.Diagnostics,
+ err,
+ "Reading routing table",
+ fmt.Sprintf("Routing table with ID %q or network area with ID %q does not exist in organization %q.", routingTableId, networkAreaId, organizationId),
+ map[int]string{
+ http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", organizationId),
+ },
+ )
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ err = mapDatasourceFields(ctx, routingTableResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table read")
+}
+
+func mapDatasourceFields(ctx context.Context, routingTable *iaasalpha.RoutingTable, model *shared.RoutingTableDataSourceModel, region string) error {
+ if routingTable == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ var routingTableId string
+ if model.RoutingTableId.ValueString() != "" {
+ routingTableId = model.RoutingTableId.ValueString()
+ } else if routingTable.Id != nil {
+ routingTableId = *routingTable.Id
+ } else {
+ return fmt.Errorf("routing table id not present")
+ }
+
+ idParts := []string{
+ model.OrganizationId.ValueString(),
+ region,
+ model.NetworkAreaId.ValueString(),
+ routingTableId,
+ }
+ model.Id = types.StringValue(
+ strings.Join(idParts, core.Separator),
+ )
+
+ err := shared.MapRoutingTableReadModel(ctx, routingTable, &model.RoutingTableReadModel)
+ if err != nil {
+ return err
+ }
+
+ model.Region = types.StringValue(region)
+ return nil
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/table/datasource_test.go b/stackit/internal/services/iaasalpha/routingtable/table/datasource_test.go
new file mode 100644
index 00000000..4622e1b3
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/table/datasource_test.go
@@ -0,0 +1,136 @@
+package table
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+)
+
+const (
+ testRegion = "eu01"
+)
+
+var (
+ organizationId = uuid.New()
+ networkAreaId = uuid.New()
+ routingTableId = uuid.New()
+)
+
+func Test_mapDatasourceFields(t *testing.T) {
+ id := fmt.Sprintf("%s,%s,%s,%s", organizationId.String(), testRegion, networkAreaId.String(), routingTableId.String())
+
+ tests := []struct {
+ description string
+ state shared.RoutingTableDataSourceModel
+ input *iaasalpha.RoutingTable
+ expected shared.RoutingTableDataSourceModel
+ isValid bool
+ }{
+ {
+ "default_values",
+ shared.RoutingTableDataSourceModel{
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ },
+ &iaasalpha.RoutingTable{
+ Id: utils.Ptr(routingTableId.String()),
+ Name: utils.Ptr("default_values"),
+ },
+ shared.RoutingTableDataSourceModel{
+ Id: types.StringValue(id),
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ RoutingTableReadModel: shared.RoutingTableReadModel{
+ RoutingTableId: types.StringValue(routingTableId.String()),
+ Name: types.StringValue("default_values"),
+ Labels: types.MapNull(types.StringType),
+ },
+ },
+ true,
+ },
+ {
+ "values_ok",
+ shared.RoutingTableDataSourceModel{
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ RoutingTableReadModel: shared.RoutingTableReadModel{},
+ },
+ &iaasalpha.RoutingTable{
+ Id: utils.Ptr(routingTableId.String()),
+ Name: utils.Ptr("values_ok"),
+ Description: utils.Ptr("Description"),
+ Labels: &map[string]interface{}{
+ "key": "value",
+ },
+ },
+ shared.RoutingTableDataSourceModel{
+ Id: types.StringValue(id),
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ RoutingTableReadModel: shared.RoutingTableReadModel{
+ RoutingTableId: types.StringValue(routingTableId.String()),
+ Name: types.StringValue("values_ok"),
+ Description: types.StringValue("Description"),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "key": types.StringValue("value"),
+ }),
+ },
+ },
+ true,
+ },
+ {
+ "response_fields_nil_fail",
+ shared.RoutingTableDataSourceModel{},
+ &iaasalpha.RoutingTable{
+ Id: nil,
+ },
+ shared.RoutingTableDataSourceModel{},
+ false,
+ },
+ {
+ "response_nil_fail",
+ shared.RoutingTableDataSourceModel{},
+ nil,
+ shared.RoutingTableDataSourceModel{},
+ false,
+ },
+ {
+ "no_resource_id",
+ shared.RoutingTableDataSourceModel{
+ OrganizationId: types.StringValue("oid"),
+ NetworkAreaId: types.StringValue("naid"),
+ },
+ &iaasalpha.RoutingTable{},
+ shared.RoutingTableDataSourceModel{},
+ false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ err := mapDatasourceFields(context.Background(), tt.input, &tt.state, testRegion)
+ if !tt.isValid && err == nil {
+ t.Fatalf("Should have failed")
+ }
+ if tt.isValid && err != nil {
+ t.Fatalf("Should not have failed: %v", err)
+ }
+ if tt.isValid {
+ diff := cmp.Diff(tt.state, tt.expected)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/table/resource.go b/stackit/internal/services/iaasalpha/routingtable/table/resource.go
new file mode 100644
index 00000000..c83c4e1d
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/table/resource.go
@@ -0,0 +1,479 @@
+package table
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
+
+ iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
+
+ "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/planmodifier"
+ "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"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ iaasalphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &routingTableResource{}
+ _ resource.ResourceWithConfigure = &routingTableResource{}
+ _ resource.ResourceWithImportState = &routingTableResource{}
+)
+
+type Model struct {
+ Id types.String `tfsdk:"id"` // needed by TF
+ OrganizationId types.String `tfsdk:"organization_id"`
+ RoutingTableId types.String `tfsdk:"routing_table_id"`
+ Name types.String `tfsdk:"name"`
+ NetworkAreaId types.String `tfsdk:"network_area_id"`
+ Description types.String `tfsdk:"description"`
+ Labels types.Map `tfsdk:"labels"`
+ Region types.String `tfsdk:"region"`
+ SystemRoutes types.Bool `tfsdk:"system_routes"`
+ CreatedAt types.String `tfsdk:"created_at"`
+ UpdatedAt types.String `tfsdk:"updated_at"`
+}
+
+// NewRoutingTableResource is a helper function to simplify the provider implementation.
+func NewRoutingTableResource() resource.Resource {
+ return &routingTableResource{}
+}
+
+// routingTableResource is the resource implementation.
+type routingTableResource struct {
+ client *iaasalpha.APIClient
+ providerData core.ProviderData
+}
+
+// Metadata returns the resource type name.
+func (r *routingTableResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_routing_table"
+}
+
+// Configure adds the provider configured client to the resource.
+func (r *routingTableResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ var ok bool
+ r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckExperimentEnabled(ctx, &r.providerData, features.RoutingTablesExperiment, "stackit_routing_table", core.Resource, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := iaasalphaUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ r.client = apiClient
+ tflog.Info(ctx, "IaaS alpha client configured")
+}
+
+func (r *routingTableResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ description := "Routing table resource schema. Must have a `region` specified in the provider configuration."
+ resp.Schema = schema.Schema{
+ Description: description,
+ MarkdownDescription: features.AddExperimentDescription(description, features.RoutingTablesExperiment, core.Resource),
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Terraform's internal resource ID. It is structured as \"`organization_id`,`region`,`network_area_id`,`routing_table_id`\".",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "organization_id": schema.StringAttribute{
+ Description: "STACKIT organization ID to which the routing table is associated.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "routing_table_id": schema.StringAttribute{
+ Description: "The routing tables ID.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "name": schema.StringAttribute{
+ Description: "The name of the routing table.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ stringvalidator.LengthAtMost(63),
+ },
+ },
+ "network_area_id": schema.StringAttribute{
+ Description: "The network area ID to which the routing table is associated.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "description": schema.StringAttribute{
+ Description: "Description of the routing table.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtMost(127),
+ },
+ },
+ "labels": schema.MapAttribute{
+ Description: "Labels are key-value string pairs which can be attached to a resource container",
+ ElementType: types.StringType,
+ Optional: true,
+ },
+ "region": schema.StringAttribute{
+ Optional: true,
+ // must be computed to allow for storing the override value from the provider
+ Computed: true,
+ Description: "The resource region. If not defined, the provider region is used.",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "system_routes": schema.BoolAttribute{
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(true),
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.RequiresReplace(),
+ },
+ },
+ "created_at": schema.StringAttribute{
+ Description: "Date-time when the routing table was created",
+ Computed: true,
+ },
+ "updated_at": schema.StringAttribute{
+ Description: "Date-time when the routing table was updated",
+ Computed: true,
+ },
+ },
+ }
+}
+
+// Create creates the resource and sets the initial Terraform state.
+func (r *routingTableResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
+ // Retrieve values from plan
+ var model Model
+ diags := req.Plan.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+
+ // Generate API request body from model
+ payload, err := toCreatePayload(ctx, &model)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating routing table", fmt.Sprintf("Creating API payload: %v", err))
+ return
+ }
+
+ routingTable, err := r.client.AddRoutingTableToArea(ctx, organizationId, networkAreaId, region).AddRoutingTableToAreaPayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating routing table", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ // Map response body to schema
+ err = mapFields(ctx, routingTable, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating routing table.", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ // Set state to fully populated data
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table created")
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (r *routingTableResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model Model
+ diags := req.State.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+
+ routingTableResp, err := r.client.GetRoutingTableOfArea(ctx, organizationId, networkAreaId, region, routingTableId).Execute()
+ if err != nil {
+ utils.LogError(
+ ctx,
+ &resp.Diagnostics,
+ err,
+ "Reading routing table",
+ fmt.Sprintf("routing table with ID %q does not exist in organization %q.", routingTableId, organizationId),
+ map[int]string{
+ http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", organizationId),
+ },
+ )
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ // Map response body to schema
+ err = mapFields(ctx, routingTableResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ // Set refreshed state
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table read")
+}
+
+// Update updates the resource and sets the updated Terraform state on success.
+func (r *routingTableResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
+ // Retrieve values from plan
+ var model Model
+ diags := req.Plan.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+
+ // Retrieve values from state
+ var stateModel Model
+ diags = req.State.Get(ctx, &stateModel)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Generate API request body from model
+ payload, err := toUpdatePayload(ctx, &model, stateModel.Labels)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating routing table", fmt.Sprintf("Creating API payload: %v", err))
+ return
+ }
+
+ routingTable, err := r.client.UpdateRoutingTableOfArea(ctx, organizationId, networkAreaId, region, routingTableId).UpdateRoutingTableOfAreaPayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating routing table", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ // Map response body to schema
+ err = mapFields(ctx, routingTable, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating routing table", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ // Set refreshed state
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table updated")
+}
+
+// Delete deletes the resource and removes the Terraform state on success.
+func (r *routingTableResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
+ // Retrieve values from state
+ var model Model
+ diags := req.State.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+ routingTableId := model.RoutingTableId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+
+ // Delete existing routing table
+ err := r.client.DeleteRoutingTableFromArea(ctx, organizationId, networkAreaId, region, routingTableId).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting routing table", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ tflog.Info(ctx, "Routing table deleted")
+}
+
+// ImportState imports a resource into the Terraform state on success.
+// The expected format of the resource import identifier is: organization_id,region,network_area_id,routing_table_id
+func (r *routingTableResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ idParts := strings.Split(req.ID, core.Separator)
+
+ if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
+ core.LogAndAddError(ctx, &resp.Diagnostics,
+ "Error importing routing table",
+ fmt.Sprintf("Expected import identifier with format: [organization_id],[region],[network_area_id],[routing_table_id] Got: %q", req.ID),
+ )
+ return
+ }
+
+ organizationId := idParts[0]
+ region := idParts[1]
+ networkAreaId := idParts[2]
+ routingTableId := idParts[3]
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+ ctx = tflog.SetField(ctx, "routing_table_id", routingTableId)
+
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization_id"), organizationId)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_area_id"), networkAreaId)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("routing_table_id"), routingTableId)...)
+ tflog.Info(ctx, "Routing table state imported")
+}
+
+func mapFields(ctx context.Context, routingTable *iaasalpha.RoutingTable, model *Model, region string) error {
+ if routingTable == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ var routingTableId string
+ if model.RoutingTableId.ValueString() != "" {
+ routingTableId = model.RoutingTableId.ValueString()
+ } else if routingTable.Id != nil {
+ routingTableId = *routingTable.Id
+ } else {
+ return fmt.Errorf("routing table id not present")
+ }
+
+ model.Id = utils.BuildInternalTerraformId(model.OrganizationId.ValueString(), region, model.NetworkAreaId.ValueString(), routingTableId)
+
+ labels, err := iaasUtils.MapLabels(ctx, routingTable.Labels, model.Labels)
+ if err != nil {
+ return err
+ }
+
+ // created at and updated at
+ createdAtTF, updatedAtTF := types.StringNull(), types.StringNull()
+ if routingTable.CreatedAt != nil {
+ createdAtValue := *routingTable.CreatedAt
+ createdAtTF = types.StringValue(createdAtValue.Format(time.RFC3339))
+ }
+ if routingTable.UpdatedAt != nil {
+ updatedAtValue := *routingTable.UpdatedAt
+ updatedAtTF = types.StringValue(updatedAtValue.Format(time.RFC3339))
+ }
+
+ model.RoutingTableId = types.StringValue(routingTableId)
+ model.Name = types.StringPointerValue(routingTable.Name)
+ model.Description = types.StringPointerValue(routingTable.Description)
+ model.Labels = labels
+ model.Region = types.StringValue(region)
+ model.SystemRoutes = types.BoolPointerValue(routingTable.SystemRoutes)
+ model.CreatedAt = createdAtTF
+ model.UpdatedAt = updatedAtTF
+ return nil
+}
+
+func toCreatePayload(ctx context.Context, model *Model) (*iaasalpha.AddRoutingTableToAreaPayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
+ if err != nil {
+ return nil, fmt.Errorf("converting to Go map: %w", err)
+ }
+
+ return &iaasalpha.AddRoutingTableToAreaPayload{
+ Description: conversion.StringValueToPointer(model.Description),
+ Name: conversion.StringValueToPointer(model.Name),
+ Labels: &labels,
+ SystemRoutes: conversion.BoolValueToPointer(model.SystemRoutes),
+ }, nil
+}
+
+func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map) (*iaasalpha.UpdateRoutingTableOfAreaPayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
+ if err != nil {
+ return nil, fmt.Errorf("converting to Go map: %w", err)
+ }
+
+ return &iaasalpha.UpdateRoutingTableOfAreaPayload{
+ Description: conversion.StringValueToPointer(model.Description),
+ Name: conversion.StringValueToPointer(model.Name),
+ Labels: &labels,
+ }, nil
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/table/resource_test.go b/stackit/internal/services/iaasalpha/routingtable/table/resource_test.go
new file mode 100644
index 00000000..24b1fef9
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/table/resource_test.go
@@ -0,0 +1,212 @@
+package table
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+)
+
+func TestMapFields(t *testing.T) {
+ const testRegion = "eu01"
+ id := fmt.Sprintf("%s,%s,%s,%s", "oid", testRegion, "aid", "rtid")
+ tests := []struct {
+ description string
+ state Model
+ input *iaasalpha.RoutingTable
+ expected Model
+ isValid bool
+ }{
+ {
+ "default_values",
+ Model{
+ OrganizationId: types.StringValue("oid"),
+ NetworkAreaId: types.StringValue("aid"),
+ },
+ &iaasalpha.RoutingTable{
+ Id: utils.Ptr("rtid"),
+ Name: utils.Ptr("default_values"),
+ },
+ Model{
+ Id: types.StringValue(id),
+ OrganizationId: types.StringValue("oid"),
+ RoutingTableId: types.StringValue("rtid"),
+ Name: types.StringValue("default_values"),
+ NetworkAreaId: types.StringValue("aid"),
+ Labels: types.MapNull(types.StringType),
+ Region: types.StringValue(testRegion),
+ },
+ true,
+ },
+ {
+ "values_ok",
+ Model{
+ OrganizationId: types.StringValue("oid"),
+ NetworkAreaId: types.StringValue("aid"),
+ },
+ &iaasalpha.RoutingTable{
+ Id: utils.Ptr("rtid"),
+ Name: utils.Ptr("values_ok"),
+ Description: utils.Ptr("Description"),
+ Labels: &map[string]interface{}{
+ "key": "value",
+ },
+ },
+ Model{
+ Id: types.StringValue(id),
+ OrganizationId: types.StringValue("oid"),
+ RoutingTableId: types.StringValue("rtid"),
+ Name: types.StringValue("values_ok"),
+ Description: types.StringValue("Description"),
+ NetworkAreaId: types.StringValue("aid"),
+ Region: types.StringValue(testRegion),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "key": types.StringValue("value"),
+ }),
+ },
+ true,
+ },
+ {
+ "response_fields_nil_fail",
+ Model{},
+ &iaasalpha.RoutingTable{
+ Id: nil,
+ },
+ Model{},
+ false,
+ },
+ {
+ "response_nil_fail",
+ Model{},
+ nil,
+ Model{},
+ false,
+ },
+ {
+ "no_resource_id",
+ Model{
+ OrganizationId: types.StringValue("oid"),
+ NetworkAreaId: types.StringValue("naid"),
+ },
+ &iaasalpha.RoutingTable{},
+ Model{},
+ false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ err := mapFields(context.Background(), tt.input, &tt.state, testRegion)
+ if !tt.isValid && err == nil {
+ t.Fatalf("Should have failed")
+ }
+ if tt.isValid && err != nil {
+ t.Fatalf("Should not have failed: %v", err)
+ }
+ if tt.isValid {
+ diff := cmp.Diff(tt.state, tt.expected)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
+
+func TestToCreatePayload(t *testing.T) {
+ tests := []struct {
+ description string
+ input *Model
+ expected *iaasalpha.AddRoutingTableToAreaPayload
+ isValid bool
+ }{
+ {
+ description: "default_ok",
+ input: &Model{
+ Description: types.StringValue("Description"),
+ Name: types.StringValue("default_ok"),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "key": types.StringValue("value"),
+ }),
+ SystemRoutes: types.BoolValue(true),
+ },
+ expected: &iaasalpha.AddRoutingTableToAreaPayload{
+ Description: utils.Ptr("Description"),
+ Name: utils.Ptr("default_ok"),
+ Labels: &map[string]interface{}{
+ "key": "value",
+ },
+ SystemRoutes: utils.Ptr(true),
+ },
+ isValid: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ output, err := toCreatePayload(context.Background(), tt.input)
+ if !tt.isValid && err == nil {
+ t.Fatalf("Should have failed")
+ }
+ if tt.isValid && err != nil {
+ t.Fatalf("Should not have failed: %v", err)
+ }
+ if tt.isValid {
+ diff := cmp.Diff(output, tt.expected)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
+
+func TestToUpdatePayload(t *testing.T) {
+ tests := []struct {
+ description string
+ input *Model
+ expected *iaasalpha.UpdateRoutingTableOfAreaPayload
+ isValid bool
+ }{
+ {
+ "default_ok",
+ &Model{
+ Description: types.StringValue("Description"),
+ Name: types.StringValue("default_ok"),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "key1": types.StringValue("value1"),
+ "key2": types.StringValue("value2"),
+ }),
+ },
+ &iaasalpha.UpdateRoutingTableOfAreaPayload{
+ Description: utils.Ptr("Description"),
+ Name: utils.Ptr("default_ok"),
+ Labels: &map[string]interface{}{
+ "key1": "value1",
+ "key2": "value2",
+ },
+ },
+ true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ output, err := toUpdatePayload(context.Background(), tt.input, types.MapNull(types.StringType))
+ if !tt.isValid && err == nil {
+ t.Fatalf("Should have failed")
+ }
+ if tt.isValid && err != nil {
+ t.Fatalf("Should not have failed: %v", err)
+ }
+ if tt.isValid {
+ diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaasalpha.NullableString{}))
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/tables/datasource.go b/stackit/internal/services/iaasalpha/routingtable/tables/datasource.go
new file mode 100644
index 00000000..eac2257c
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/tables/datasource.go
@@ -0,0 +1,212 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "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/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ iaasalphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &routingTablesDataSource{}
+)
+
+type DataSourceModelTables struct {
+ Id types.String `tfsdk:"id"` // needed by TF
+ OrganizationId types.String `tfsdk:"organization_id"`
+ NetworkAreaId types.String `tfsdk:"network_area_id"`
+ Region types.String `tfsdk:"region"`
+ Items types.List `tfsdk:"items"`
+}
+
+// NewRoutingTablesDataSource is a helper function to simplify the provider implementation.
+func NewRoutingTablesDataSource() datasource.DataSource {
+ return &routingTablesDataSource{}
+}
+
+// routingTableDataSource is the data source implementation.
+type routingTablesDataSource struct {
+ client *iaasalpha.APIClient
+ providerData core.ProviderData
+}
+
+// Metadata returns the data source type name.
+func (d *routingTablesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_routing_tables"
+}
+
+func (d *routingTablesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckExperimentEnabled(ctx, &d.providerData, features.RoutingTablesExperiment, "stackit_routing_tables", core.Datasource, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := iaasalphaUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ d.client = apiClient
+ tflog.Info(ctx, "IaaS client configured")
+}
+
+// Schema defines the schema for the data source.
+func (d *routingTablesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ description := "Routing table datasource schema. Must have a `region` specified in the provider configuration."
+ resp.Schema = schema.Schema{
+ Description: description,
+ MarkdownDescription: features.AddExperimentDescription(description, features.RoutingTablesExperiment, core.Datasource),
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Terraform's internal datasource ID. It is structured as \"`organization_id`,`region`,`network_area_id`\".",
+ Computed: true,
+ },
+ "organization_id": schema.StringAttribute{
+ Description: "STACKIT organization ID to which the routing table is associated.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "network_area_id": schema.StringAttribute{
+ Description: "The network area ID to which the routing table is associated.",
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "region": schema.StringAttribute{
+ Description: "The resource region. If not defined, the provider region is used.",
+ // the region cannot be found, so it has to be passed
+ Optional: true,
+ },
+ "items": schema.ListNestedAttribute{
+ Description: "List of routing tables.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: shared.RoutingTableResponseAttributes(),
+ },
+ },
+ },
+ }
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (d *routingTablesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model DataSourceModelTables
+ diags := req.Config.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ region := d.providerData.GetRegionWithOverride(model.Region)
+ networkAreaId := model.NetworkAreaId.ValueString()
+ ctx = tflog.SetField(ctx, "organization_id", organizationId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "network_area_id", networkAreaId)
+
+ routingTablesResp, err := d.client.ListRoutingTablesOfArea(ctx, organizationId, networkAreaId, region).Execute()
+ if err != nil {
+ utils.LogError(
+ ctx,
+ &resp.Diagnostics,
+ err,
+ "Reading routing tables",
+ fmt.Sprintf("Routing tables with network area with ID %q does not exist in organization %q.", networkAreaId, organizationId),
+ map[int]string{
+ http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", organizationId),
+ },
+ )
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ err = mapDataSourceRoutingTables(ctx, routingTablesResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading routing table", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "Routing table read")
+}
+
+func mapDataSourceRoutingTables(ctx context.Context, routingTables *iaasalpha.RoutingTableListResponse, model *DataSourceModelTables, region string) error {
+ if routingTables == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+ if routingTables.Items == nil {
+ return fmt.Errorf("items input is nil")
+ }
+
+ organizationId := model.OrganizationId.ValueString()
+ networkAreaId := model.NetworkAreaId.ValueString()
+
+ model.Id = utils.BuildInternalTerraformId(organizationId, region, networkAreaId)
+
+ itemsList := []attr.Value{}
+ for i, routingTable := range *routingTables.Items {
+ var routingTableModel shared.RoutingTableReadModel
+ err := shared.MapRoutingTableReadModel(ctx, &routingTable, &routingTableModel)
+ if err != nil {
+ return fmt.Errorf("mapping routes: %w", err)
+ }
+
+ routingTableMap := map[string]attr.Value{
+ "routing_table_id": routingTableModel.RoutingTableId,
+ "name": routingTableModel.Name,
+ "description": routingTableModel.Description,
+ "labels": routingTableModel.Labels,
+ "created_at": routingTableModel.CreatedAt,
+ "updated_at": routingTableModel.UpdatedAt,
+ "default": routingTableModel.Default,
+ "system_routes": routingTableModel.SystemRoutes,
+ }
+
+ routingTableTF, diags := types.ObjectValue(shared.RoutingTableReadModelTypes(), routingTableMap)
+ if diags.HasError() {
+ return fmt.Errorf("mapping index %d: %w", i, core.DiagsToError(diags))
+ }
+ itemsList = append(itemsList, routingTableTF)
+ }
+
+ itemsListTF, diags := types.ListValue(types.ObjectType{AttrTypes: shared.RoutingTableReadModelTypes()}, itemsList)
+ if diags.HasError() {
+ return core.DiagsToError(diags)
+ }
+ model.Items = itemsListTF
+ model.Region = types.StringValue(region)
+
+ return nil
+}
diff --git a/stackit/internal/services/iaasalpha/routingtable/tables/datasource_test.go b/stackit/internal/services/iaasalpha/routingtable/tables/datasource_test.go
new file mode 100644
index 00000000..2df93e79
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/routingtable/tables/datasource_test.go
@@ -0,0 +1,175 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/shared"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+)
+
+const (
+ testRegion = "eu01"
+)
+
+var (
+ organizationId = uuid.New()
+ networkAreaId = uuid.New()
+ routingTableId = uuid.New()
+ secondRoutingTableId = uuid.New()
+)
+
+func TestMapDataFields(t *testing.T) {
+ terraformId := fmt.Sprintf("%s,%s,%s", organizationId.String(), testRegion, networkAreaId.String())
+ createdAt := time.Now()
+ updatedAt := time.Now().Add(5 * time.Minute)
+
+ tests := []struct {
+ description string
+ state DataSourceModelTables
+ input *iaasalpha.RoutingTableListResponse
+ expected DataSourceModelTables
+ isValid bool
+ }{
+ {
+ "default_values",
+ DataSourceModelTables{
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ },
+ &iaasalpha.RoutingTableListResponse{
+ Items: &[]iaasalpha.RoutingTable{
+ {
+ Id: utils.Ptr(routingTableId.String()),
+ Name: utils.Ptr("test"),
+ Description: utils.Ptr("description"),
+ Default: utils.Ptr(true),
+ CreatedAt: &createdAt,
+ UpdatedAt: &updatedAt,
+ SystemRoutes: utils.Ptr(false),
+ },
+ },
+ },
+ DataSourceModelTables{
+ Id: types.StringValue(terraformId),
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ Items: types.ListValueMust(types.ObjectType{AttrTypes: shared.RoutingTableReadModelTypes()}, []attr.Value{
+ types.ObjectValueMust(shared.RoutingTableReadModelTypes(), map[string]attr.Value{
+ "routing_table_id": types.StringValue(routingTableId.String()),
+ "name": types.StringValue("test"),
+ "description": types.StringValue("description"),
+ "default": types.BoolValue(true),
+ "system_routes": types.BoolValue(false),
+ "created_at": types.StringValue(createdAt.Format(time.RFC3339)),
+ "updated_at": types.StringValue(updatedAt.Format(time.RFC3339)),
+ "labels": types.MapNull(types.StringType),
+ }),
+ }),
+ },
+ true,
+ },
+ {
+ "two routing tables",
+ DataSourceModelTables{
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ },
+ &iaasalpha.RoutingTableListResponse{
+ Items: &[]iaasalpha.RoutingTable{
+ {
+ Id: utils.Ptr(routingTableId.String()),
+ Name: utils.Ptr("test"),
+ Description: utils.Ptr("description"),
+ Default: utils.Ptr(true),
+ CreatedAt: &createdAt,
+ UpdatedAt: &updatedAt,
+ SystemRoutes: utils.Ptr(false),
+ },
+ {
+ Id: utils.Ptr(secondRoutingTableId.String()),
+ Name: utils.Ptr("test2"),
+ Description: utils.Ptr("description2"),
+ Default: utils.Ptr(false),
+ CreatedAt: &createdAt,
+ UpdatedAt: &updatedAt,
+ SystemRoutes: utils.Ptr(false),
+ },
+ },
+ },
+ DataSourceModelTables{
+ Id: types.StringValue(terraformId),
+ OrganizationId: types.StringValue(organizationId.String()),
+ NetworkAreaId: types.StringValue(networkAreaId.String()),
+ Region: types.StringValue(testRegion),
+ Items: types.ListValueMust(types.ObjectType{AttrTypes: shared.RoutingTableReadModelTypes()}, []attr.Value{
+ types.ObjectValueMust(shared.RoutingTableReadModelTypes(), map[string]attr.Value{
+ "routing_table_id": types.StringValue(routingTableId.String()),
+ "name": types.StringValue("test"),
+ "description": types.StringValue("description"),
+ "default": types.BoolValue(true),
+ "system_routes": types.BoolValue(false),
+ "created_at": types.StringValue(createdAt.Format(time.RFC3339)),
+ "updated_at": types.StringValue(updatedAt.Format(time.RFC3339)),
+ "labels": types.MapNull(types.StringType),
+ }),
+ types.ObjectValueMust(shared.RoutingTableReadModelTypes(), map[string]attr.Value{
+ "routing_table_id": types.StringValue(secondRoutingTableId.String()),
+ "name": types.StringValue("test2"),
+ "description": types.StringValue("description2"),
+ "default": types.BoolValue(false),
+ "system_routes": types.BoolValue(false),
+ "created_at": types.StringValue(createdAt.Format(time.RFC3339)),
+ "updated_at": types.StringValue(updatedAt.Format(time.RFC3339)),
+ "labels": types.MapNull(types.StringType),
+ }),
+ }),
+ },
+ true,
+ },
+ {
+ "response_fields_items_nil_fail",
+ DataSourceModelTables{},
+ &iaasalpha.RoutingTableListResponse{
+ Items: nil,
+ },
+ DataSourceModelTables{},
+ false,
+ },
+ {
+ "response_nil_fail",
+ DataSourceModelTables{},
+ nil,
+ DataSourceModelTables{},
+ false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ err := mapDataSourceRoutingTables(context.Background(), tt.input, &tt.state, testRegion)
+ if !tt.isValid && err == nil {
+ t.Fatalf("Should have failed")
+ }
+ if tt.isValid && err != nil {
+ t.Fatalf("Should not have failed: %v", err)
+ }
+ if tt.isValid {
+ diff := cmp.Diff(tt.state, tt.expected)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/testdata/resource-routingtable-max.tf b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-max.tf
new file mode 100644
index 00000000..74c656d1
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-max.tf
@@ -0,0 +1,19 @@
+variable "organization_id" {}
+variable "network_area_id" {}
+variable "name" {}
+variable "description" {}
+variable "region" {}
+variable "label" {}
+variable "system_routes" {}
+
+resource "stackit_routing_table" "routing_table" {
+ organization_id = var.organization_id
+ network_area_id = var.network_area_id
+ name = var.name
+ description = var.description
+ region = var.region
+ labels = {
+ "acc-test" : var.label
+ }
+ system_routes = var.system_routes
+}
diff --git a/stackit/internal/services/iaasalpha/testdata/resource-routingtable-min.tf b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-min.tf
new file mode 100644
index 00000000..26921d7d
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-min.tf
@@ -0,0 +1,9 @@
+variable "organization_id" {}
+variable "network_area_id" {}
+variable "name" {}
+
+resource "stackit_routing_table" "routing_table" {
+ organization_id = var.organization_id
+ network_area_id = var.network_area_id
+ name = var.name
+}
diff --git a/stackit/internal/services/iaasalpha/testdata/resource-routingtable-route-max.tf b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-route-max.tf
new file mode 100644
index 00000000..da2833c0
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-route-max.tf
@@ -0,0 +1,31 @@
+variable "organization_id" {}
+variable "network_area_id" {}
+variable "routing_table_name" {}
+variable "destination_type" {}
+variable "destination_value" {}
+variable "next_hop_type" {}
+variable "next_hop_value" {}
+variable "label" {}
+
+resource "stackit_routing_table" "routing_table" {
+ organization_id = var.organization_id
+ network_area_id = var.network_area_id
+ name = var.routing_table_name
+}
+
+resource "stackit_routing_table_route" "route" {
+ organization_id = var.organization_id
+ network_area_id = var.network_area_id
+ routing_table_id = stackit_routing_table.routing_table.routing_table_id
+ destination = {
+ type = var.destination_type
+ value = var.destination_value
+ }
+ next_hop = {
+ type = var.next_hop_type
+ value = var.next_hop_value
+ }
+ labels = {
+ "acc-test" = var.label
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/testdata/resource-routingtable-route-min.tf b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-route-min.tf
new file mode 100644
index 00000000..65336be8
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/testdata/resource-routingtable-route-min.tf
@@ -0,0 +1,27 @@
+variable "organization_id" {}
+variable "network_area_id" {}
+variable "routing_table_name" {}
+variable "destination_type" {}
+variable "destination_value" {}
+variable "next_hop_type" {}
+variable "next_hop_value" {}
+
+resource "stackit_routing_table" "routing_table" {
+ organization_id = var.organization_id
+ network_area_id = var.network_area_id
+ name = var.routing_table_name
+}
+
+resource "stackit_routing_table_route" "route" {
+ organization_id = var.organization_id
+ network_area_id = var.network_area_id
+ routing_table_id = stackit_routing_table.routing_table.routing_table_id
+ destination = {
+ type = var.destination_type
+ value = var.destination_value
+ }
+ next_hop = {
+ type = var.next_hop_type
+ value = var.next_hop_value
+ }
+}
diff --git a/stackit/internal/services/iaasalpha/utils/util.go b/stackit/internal/services/iaasalpha/utils/util.go
new file mode 100644
index 00000000..40216b92
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/utils/util.go
@@ -0,0 +1,29 @@
+package utils
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/stackitcloud/stackit-sdk-go/core/config"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+)
+
+func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *iaasalpha.APIClient {
+ apiClientConfigOptions := []config.ConfigurationOption{
+ config.WithCustomAuth(providerData.RoundTripper),
+ utils.UserAgentConfigOption(providerData.Version),
+ }
+ if providerData.IaaSCustomEndpoint != "" {
+ apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.IaaSCustomEndpoint))
+ }
+ apiClient, err := iaasalpha.NewAPIClient(apiClientConfigOptions...)
+ if err != nil {
+ core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err))
+ return nil
+ }
+
+ return apiClient
+}
diff --git a/stackit/internal/services/iaasalpha/utils/util_test.go b/stackit/internal/services/iaasalpha/utils/util_test.go
new file mode 100644
index 00000000..d4ba4671
--- /dev/null
+++ b/stackit/internal/services/iaasalpha/utils/util_test.go
@@ -0,0 +1,93 @@
+package utils
+
+import (
+ "context"
+ "os"
+ "reflect"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients"
+ "github.com/stackitcloud/stackit-sdk-go/core/config"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+)
+
+const (
+ testVersion = "1.2.3"
+ testCustomEndpoint = "https://iaas-custom-endpoint.api.stackit.cloud"
+)
+
+func TestConfigureClient(t *testing.T) {
+ /* mock authentication by setting service account token env variable */
+ os.Clearenv()
+ err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val")
+ if err != nil {
+ t.Errorf("error setting env variable: %v", err)
+ }
+
+ type args struct {
+ providerData *core.ProviderData
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expected *iaasalpha.APIClient
+ }{
+ {
+ name: "default endpoint",
+ args: args{
+ providerData: &core.ProviderData{
+ Version: testVersion,
+ },
+ },
+ expected: func() *iaasalpha.APIClient {
+ apiClient, err := iaasalpha.NewAPIClient(
+ utils.UserAgentConfigOption(testVersion),
+ )
+ if err != nil {
+ t.Errorf("error configuring client: %v", err)
+ }
+ return apiClient
+ }(),
+ wantErr: false,
+ },
+ {
+ name: "custom endpoint",
+ args: args{
+ providerData: &core.ProviderData{
+ Version: testVersion,
+ IaaSCustomEndpoint: testCustomEndpoint,
+ },
+ },
+ expected: func() *iaasalpha.APIClient {
+ apiClient, err := iaasalpha.NewAPIClient(
+ utils.UserAgentConfigOption(testVersion),
+ config.WithEndpoint(testCustomEndpoint),
+ )
+ if err != nil {
+ t.Errorf("error configuring client: %v", err)
+ }
+ return apiClient
+ }(),
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ diags := diag.Diagnostics{}
+
+ actual := ConfigureClient(ctx, tt.args.providerData, &diags)
+ if diags.HasError() != tt.wantErr {
+ t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr)
+ }
+
+ if !reflect.DeepEqual(actual, tt.expected) {
+ t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/serverbackup/schedule/resource.go b/stackit/internal/services/serverbackup/schedule/resource.go
index 82168ef8..cc0ee969 100644
--- a/stackit/internal/services/serverbackup/schedule/resource.go
+++ b/stackit/internal/services/serverbackup/schedule/resource.go
@@ -129,7 +129,7 @@ func (r *scheduleResource) Configure(ctx context.Context, req resource.Configure
func (r *scheduleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Server backup schedule resource schema. Must have a `region` specified in the provider configuration.",
- MarkdownDescription: features.AddBetaDescription("Server backup schedule resource schema. Must have a `region` specified in the provider configuration."),
+ MarkdownDescription: features.AddBetaDescription("Server backup schedule resource schema. Must have a `region` specified in the provider configuration.", core.Resource),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`server_id`,`backup_schedule_id`\".",
diff --git a/stackit/internal/services/serverbackup/schedule/schedule_datasource.go b/stackit/internal/services/serverbackup/schedule/schedule_datasource.go
index d05fc807..2d3f82cf 100644
--- a/stackit/internal/services/serverbackup/schedule/schedule_datasource.go
+++ b/stackit/internal/services/serverbackup/schedule/schedule_datasource.go
@@ -77,7 +77,7 @@ func (r *scheduleDataSource) Configure(ctx context.Context, req datasource.Confi
func (r *scheduleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Server backup schedule datasource schema. Must have a `region` specified in the provider configuration.",
- MarkdownDescription: features.AddBetaDescription("Server backup schedule datasource schema. Must have a `region` specified in the provider configuration."),
+ MarkdownDescription: features.AddBetaDescription("Server backup schedule datasource schema. Must have a `region` specified in the provider configuration.", core.Datasource),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`server_id`,`backup_schedule_id`\".",
diff --git a/stackit/internal/services/serverbackup/schedule/schedules_datasource.go b/stackit/internal/services/serverbackup/schedule/schedules_datasource.go
index 5935ef8b..81cd5ade 100644
--- a/stackit/internal/services/serverbackup/schedule/schedules_datasource.go
+++ b/stackit/internal/services/serverbackup/schedule/schedules_datasource.go
@@ -76,7 +76,7 @@ func (r *schedulesDataSource) Configure(ctx context.Context, req datasource.Conf
func (r *schedulesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Server backup schedules datasource schema. Must have a `region` specified in the provider configuration.",
- MarkdownDescription: features.AddBetaDescription("Server backup schedules datasource schema. Must have a `region` specified in the provider configuration."),
+ MarkdownDescription: features.AddBetaDescription("Server backup schedules datasource schema. Must have a `region` specified in the provider configuration.", core.Datasource),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal data source identifier. It is structured as \"`project_id`,`server_id`\".",
diff --git a/stackit/internal/services/serverupdate/schedule/resource.go b/stackit/internal/services/serverupdate/schedule/resource.go
index c81dfe4a..2ef4e705 100644
--- a/stackit/internal/services/serverupdate/schedule/resource.go
+++ b/stackit/internal/services/serverupdate/schedule/resource.go
@@ -122,7 +122,7 @@ func (r *scheduleResource) Configure(ctx context.Context, req resource.Configure
func (r *scheduleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Server update schedule resource schema. Must have a `region` specified in the provider configuration.",
- MarkdownDescription: features.AddBetaDescription("Server update schedule resource schema. Must have a `region` specified in the provider configuration."),
+ MarkdownDescription: features.AddBetaDescription("Server update schedule resource schema. Must have a `region` specified in the provider configuration.", core.Resource),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`server_id`,`update_schedule_id`\".",
diff --git a/stackit/internal/services/serverupdate/schedule/schedule_datasource.go b/stackit/internal/services/serverupdate/schedule/schedule_datasource.go
index ef42b376..b0b17e65 100644
--- a/stackit/internal/services/serverupdate/schedule/schedule_datasource.go
+++ b/stackit/internal/services/serverupdate/schedule/schedule_datasource.go
@@ -76,7 +76,7 @@ func (r *scheduleDataSource) Configure(ctx context.Context, req datasource.Confi
func (r *scheduleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Server update schedule datasource schema. Must have a `region` specified in the provider configuration.",
- MarkdownDescription: features.AddBetaDescription("Server update schedule datasource schema. Must have a `region` specified in the provider configuration."),
+ MarkdownDescription: features.AddBetaDescription("Server update schedule datasource schema. Must have a `region` specified in the provider configuration.", core.Datasource),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`server_id`,`update_schedule_id`\".",
diff --git a/stackit/internal/services/serverupdate/schedule/schedules_datasource.go b/stackit/internal/services/serverupdate/schedule/schedules_datasource.go
index 85728cf9..4ea9a469 100644
--- a/stackit/internal/services/serverupdate/schedule/schedules_datasource.go
+++ b/stackit/internal/services/serverupdate/schedule/schedules_datasource.go
@@ -75,7 +75,7 @@ func (r *schedulesDataSource) Configure(ctx context.Context, req datasource.Conf
func (r *schedulesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Server update schedules datasource schema. Must have a `region` specified in the provider configuration.",
- MarkdownDescription: features.AddBetaDescription("Server update schedules datasource schema. Must have a `region` specified in the provider configuration."),
+ MarkdownDescription: features.AddBetaDescription("Server update schedules datasource schema. Must have a `region` specified in the provider configuration.", core.Datasource),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal data source identifier. It is structured as \"`project_id`,`region`,`server_id`\".",
diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go
index 59ee77ac..8f48353b 100644
--- a/stackit/internal/testutil/testutil.go
+++ b/stackit/internal/testutil/testutil.go
@@ -122,11 +122,13 @@ func IaaSProviderConfig() string {
return `
provider "stackit" {
default_region = "eu01"
+ experiments = ["routing-tables"]
}`
}
return fmt.Sprintf(`
provider "stackit" {
iaas_custom_endpoint = "%s"
+ experiments = ["routing-tables"]
}`,
IaaSCustomEndpoint,
)
diff --git a/stackit/internal/utils/utils.go b/stackit/internal/utils/utils.go
index 7974c44a..3368b341 100644
--- a/stackit/internal/utils/utils.go
+++ b/stackit/internal/utils/utils.go
@@ -146,7 +146,7 @@ func LogError(ctx context.Context, inputDiags *diag.Diagnostics, err error, summ
}
// FormatPossibleValues formats a slice into a comma-separated-list for usage in the provider docs
-func FormatPossibleValues(values []string) string {
+func FormatPossibleValues(values ...string) string {
var formattedValues []string
for _, value := range values {
formattedValues = append(formattedValues, fmt.Sprintf("`%v`", value))
diff --git a/stackit/internal/utils/utils_test.go b/stackit/internal/utils/utils_test.go
index 1edd232b..0a2af5c1 100644
--- a/stackit/internal/utils/utils_test.go
+++ b/stackit/internal/utils/utils_test.go
@@ -299,7 +299,7 @@ func TestFormatPossibleValues(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := FormatPossibleValues(tt.args.values); got != tt.want {
+ if got := FormatPossibleValues(tt.args.values...); got != tt.want {
t.Errorf("FormatPossibleValues() = %v, want %v", got, tt.want)
}
})
diff --git a/stackit/provider.go b/stackit/provider.go
index a3392bdf..4ef40c3c 100644
--- a/stackit/provider.go
+++ b/stackit/provider.go
@@ -40,6 +40,10 @@ import (
iaasServiceAccountAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/serviceaccountattach"
iaasVolume "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/volume"
iaasVolumeAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/volumeattach"
+ iaasalphaRoutingTableRoute "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/route"
+ iaasalphaRoutingTableRoutes "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/routes"
+ iaasalphaRoutingTable "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/table"
+ iaasalphaRoutingTables "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/tables"
loadBalancer "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/loadbalancer"
loadBalancerObservabilityCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/observability-credential"
logMeCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/credential"
@@ -457,6 +461,10 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
iaasKeyPair.NewKeyPairDataSource,
iaasServer.NewServerDataSource,
iaasSecurityGroup.NewSecurityGroupDataSource,
+ iaasalphaRoutingTable.NewRoutingTableDataSource,
+ iaasalphaRoutingTableRoute.NewRoutingTableRouteDataSource,
+ iaasalphaRoutingTables.NewRoutingTablesDataSource,
+ iaasalphaRoutingTableRoutes.NewRoutingTableRoutesDataSource,
iaasSecurityGroupRule.NewSecurityGroupRuleDataSource,
loadBalancer.NewLoadBalancerDataSource,
logMeInstance.NewInstanceDataSource,
@@ -519,6 +527,8 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
iaasServer.NewServerResource,
iaasSecurityGroup.NewSecurityGroupResource,
iaasSecurityGroupRule.NewSecurityGroupRuleResource,
+ iaasalphaRoutingTable.NewRoutingTableResource,
+ iaasalphaRoutingTableRoute.NewRoutingTableRouteResource,
loadBalancer.NewLoadBalancerResource,
loadBalancerObservabilityCredential.NewObservabilityCredentialResource,
logMeInstance.NewInstanceResource,