diff --git a/Makefile b/Makefile
index 402681aeb..11eb04c26 100644
--- a/Makefile
+++ b/Makefile
@@ -69,6 +69,7 @@ build:
# }
# }
#}
+
build-dev: $(BUILD_DEV_DIR)
$(GO) build -gcflags='all=-N -l' -o $(BUILD_DEV_BIN)
diff --git a/docs/data-sources/aws_org_vpc_peering_connection.md b/docs/data-sources/aws_org_vpc_peering_connection.md
new file mode 100644
index 000000000..aae5b935c
--- /dev/null
+++ b/docs/data-sources/aws_org_vpc_peering_connection.md
@@ -0,0 +1,36 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_aws_org_vpc_peering_connection Data Source - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Gets information about an AWS VPC peering connection.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_aws_org_vpc_peering_connection (Data Source)
+
+Gets information about an AWS VPC peering connection.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `aws_account_id` (String) AWS account ID. Changing this property forces recreation of the resource.
+- `aws_vpc_id` (String) AWS VPC ID. Changing this property forces recreation of the resource.
+- `aws_vpc_region` (String) The AWS region of the peered VPC. For example, `eu-central-1`.
+- `organization_id` (String) Identifier of the organization.
+- `organization_vpc_id` (String) Identifier of the organization VPC.
+
+### Read-Only
+
+- `aws_vpc_peering_connection_id` (String) The ID of the AWS VPC peering connection.
+- `id` (String) The ID of this resource.
+- `peering_connection_id` (String) The ID of the peering connection.
+- `state` (String) State of the peering connection. The possible values are `ACTIVE`, `APPROVED`, `APPROVED_PEER_REQUESTED`, `DELETED`, `DELETED_BY_PEER`, `DELETING`, `ERROR`, `INVALID_SPECIFICATION`, `PENDING_PEER` and `REJECTED_BY_PEER`.
diff --git a/docs/data-sources/azure_org_vpc_peering_connection.md b/docs/data-sources/azure_org_vpc_peering_connection.md
new file mode 100644
index 000000000..5a0473992
--- /dev/null
+++ b/docs/data-sources/azure_org_vpc_peering_connection.md
@@ -0,0 +1,37 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_azure_org_vpc_peering_connection Data Source - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Gets information about about an Azure VPC peering connection.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_azure_org_vpc_peering_connection (Data Source)
+
+Gets information about about an Azure VPC peering connection.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `azure_subscription_id` (String) The ID of the Azure subscription in UUID4 format. Changing this property forces recreation of the resource.
+- `organization_id` (String) Identifier of the organization.
+- `organization_vpc_id` (String) Identifier of the organization VPC.
+- `peer_resource_group` (String) The name of the Azure resource group associated with the VNet. Changing this property forces recreation of the resource.
+- `vnet_name` (String) The name of the Azure VNet. Changing this property forces recreation of the resource.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `peer_azure_app_id` (String) The ID of the Azure app that is allowed to create a peering to the Azure Virtual Network (VNet) in UUID4 format. Changing this property forces recreation of the resource.
+- `peer_azure_tenant_id` (String) The Azure tenant ID in UUID4 format. Changing this property forces recreation of the resource.
+- `peering_connection_id` (String) The ID of the cloud provider for the peering connection.
+- `state` (String) State of the peering connection
diff --git a/docs/data-sources/gcp_org_vpc_peering_connection.md b/docs/data-sources/gcp_org_vpc_peering_connection.md
new file mode 100644
index 000000000..ebfed93d7
--- /dev/null
+++ b/docs/data-sources/gcp_org_vpc_peering_connection.md
@@ -0,0 +1,34 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_gcp_org_vpc_peering_connection Data Source - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ The GCP VPC Peering Connection data source provides information about the existing Aiven VPC Peering Connection.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_gcp_org_vpc_peering_connection (Data Source)
+
+The GCP VPC Peering Connection data source provides information about the existing Aiven VPC Peering Connection.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `gcp_project_id` (String) Google Cloud project ID. Changing this property forces recreation of the resource.
+- `organization_id` (String) Identifier of the organization.
+- `organization_vpc_id` (String) Identifier of the organization VPC.
+- `peer_vpc` (String) Google Cloud VPC network name. Changing this property forces recreation of the resource.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `self_link` (String) Computed Google Cloud network peering link.
+- `state` (String) State of the peering connection.
diff --git a/docs/data-sources/organization_vpc.md b/docs/data-sources/organization_vpc.md
new file mode 100644
index 000000000..9ac732431
--- /dev/null
+++ b/docs/data-sources/organization_vpc.md
@@ -0,0 +1,35 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_organization_vpc Data Source - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Gets information about an existing VPC in an Aiven organization.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_organization_vpc (Data Source)
+
+Gets information about an existing VPC in an Aiven organization.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `organization_id` (String) The ID of the organization.
+- `organization_vpc_id` (String) The ID of the Aiven Organization VPC.
+
+### Read-Only
+
+- `cloud_name` (String) The cloud provider and region where the service is hosted in the format `CLOUD_PROVIDER-REGION_NAME`. For example, `google-europe-west1` or `aws-us-east-2`. Changing this property forces recreation of the resource.
+- `create_time` (String) Time of creation of the VPC.
+- `id` (String) The ID of this resource.
+- `network_cidr` (String) Network address range used by the VPC. For example, `192.168.0.0/24`.
+- `state` (String) State of the VPC. The possible values are `ACTIVE`, `APPROVED`, `DELETED` and `DELETING`.
+- `update_time` (String) Time of the last update of the VPC.
diff --git a/docs/resources/aws_org_vpc_peering_connection.md b/docs/resources/aws_org_vpc_peering_connection.md
new file mode 100644
index 000000000..7c611d61b
--- /dev/null
+++ b/docs/resources/aws_org_vpc_peering_connection.md
@@ -0,0 +1,75 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_aws_org_vpc_peering_connection Resource - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Creates and manages an AWS VPC peering connection with an Aiven Organization VPC.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_aws_org_vpc_peering_connection (Resource)
+
+Creates and manages an AWS VPC peering connection with an Aiven Organization VPC.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+## Example Usage
+
+```terraform
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "aws-eu-central-1"
+ network_cidr = "10.0.0.0/24"
+}
+
+resource "aiven_aws_org_vpc_peering_connection" "example_peering" {
+ organization_id = aiven_organization_vpc.example_vpc.organization_id
+ organization_vpc_id = aiven_organization_vpc.example_vpc.organization_vpc_id
+ aws_account_id = var.aws_id
+ aws_vpc_id = "vpc-1a2b3c4d5e6f7g8h9"
+ aws_vpc_region = "aws-us-east-2"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `aws_account_id` (String) AWS account ID. Changing this property forces recreation of the resource.
+- `aws_vpc_id` (String) AWS VPC ID. Changing this property forces recreation of the resource.
+- `aws_vpc_region` (String) The AWS region of the peered VPC. For example, `eu-central-1`.
+- `organization_id` (String) Identifier of the organization.
+- `organization_vpc_id` (String) Identifier of the organization VPC.
+
+### Optional
+
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `aws_vpc_peering_connection_id` (String) The ID of the AWS VPC peering connection.
+- `id` (String) The ID of this resource.
+- `peering_connection_id` (String) The ID of the peering connection.
+- `state` (String) State of the peering connection. The possible values are `ACTIVE`, `APPROVED`, `APPROVED_PEER_REQUESTED`, `DELETED`, `DELETED_BY_PEER`, `DELETING`, `ERROR`, `INVALID_SPECIFICATION`, `PENDING_PEER` and `REJECTED_BY_PEER`.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `default` (String)
+- `delete` (String)
+- `read` (String)
+- `update` (String)
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import aiven_aws_org_vpc_peering_connection.example ORGANIZATION_ID/ORGANIZATION_VPC_ID/AWS_ACCOUNT_ID/AWS_VPC_ID/AWS_REGION
+```
diff --git a/docs/resources/azure_org_vpc_peering_connection.md b/docs/resources/azure_org_vpc_peering_connection.md
new file mode 100644
index 000000000..de63fe889
--- /dev/null
+++ b/docs/resources/azure_org_vpc_peering_connection.md
@@ -0,0 +1,78 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_azure_org_vpc_peering_connection Resource - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Creates and manages an Azure VPC peering connection with an Aiven VPC.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_azure_org_vpc_peering_connection (Resource)
+
+Creates and manages an Azure VPC peering connection with an Aiven VPC.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+## Example Usage
+
+```terraform
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "azure-germany-westcentral"
+ network_cidr = "10.0.0.0/24"
+}
+
+resource "aiven_azure_org_vpc_peering_connection" "example_peering" {
+ organization_id = aiven_organization_vpc.example_vpc.organization_id
+ organization_vpc_id = aiven_organization_vpc.example_vpc.organization_vpc_id
+ azure_subscription_id = "12345678-1234-1234-1234-123456789012"
+ vnet_name = "my-vnet"
+ peer_resource_group = "my-resource-group"
+ peer_azure_app_id = "87654321-4321-4321-4321-210987654321"
+ peer_azure_tenant_id = "11111111-2222-3333-4444-555555555555"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `azure_subscription_id` (String) The ID of the Azure subscription in UUID4 format. Changing this property forces recreation of the resource.
+- `organization_id` (String) Identifier of the organization.
+- `organization_vpc_id` (String) Identifier of the organization VPC.
+- `peer_azure_app_id` (String) The ID of the Azure app that is allowed to create a peering to the Azure Virtual Network (VNet) in UUID4 format. Changing this property forces recreation of the resource.
+- `peer_azure_tenant_id` (String) The Azure tenant ID in UUID4 format. Changing this property forces recreation of the resource.
+- `peer_resource_group` (String) The name of the Azure resource group associated with the VNet. Changing this property forces recreation of the resource.
+- `vnet_name` (String) The name of the Azure VNet. Changing this property forces recreation of the resource.
+
+### Optional
+
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `peering_connection_id` (String) The ID of the cloud provider for the peering connection.
+- `state` (String) State of the peering connection
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `default` (String)
+- `delete` (String)
+- `read` (String)
+- `update` (String)
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import aiven_azure_org_vpc_peering_connection.example ORGANIZATION_ID/ORGANIZATION_VPC_ID/AZURE_SUBSCRIPTION_ID/VNET_NAME/RESOURCE_GROUP
+```
diff --git a/docs/resources/gcp_org_vpc_peering_connection.md b/docs/resources/gcp_org_vpc_peering_connection.md
new file mode 100644
index 000000000..65e04bbbb
--- /dev/null
+++ b/docs/resources/gcp_org_vpc_peering_connection.md
@@ -0,0 +1,72 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_gcp_org_vpc_peering_connection Resource - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Creates and manages a Google Cloud VPC peering connection.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_gcp_org_vpc_peering_connection (Resource)
+
+Creates and manages a Google Cloud VPC peering connection.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+## Example Usage
+
+```terraform
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "google-europe-west10"
+ network_cidr = "10.0.0.0/24"
+}
+
+resource "aiven_gcp_org_vpc_peering_connection" "example" {
+ organization_id = aiven_organization_vpc.example_vpc.organization_id
+ organization_vpc_id = aiven_organization_vpc.example_vpc.organization_vpc_id
+ gcp_project_id = "my-gcp-project-123" # Your GCP project ID
+ peer_vpc = "my-vpc-network" # Your GCP VPC network name
+}
+```
+
+
+## Schema
+
+### Required
+
+- `gcp_project_id` (String) Google Cloud project ID. Changing this property forces recreation of the resource.
+- `organization_id` (String) Identifier of the organization.
+- `organization_vpc_id` (String) Identifier of the organization VPC.
+- `peer_vpc` (String) Google Cloud VPC network name. Changing this property forces recreation of the resource.
+
+### Optional
+
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `self_link` (String) Computed Google Cloud network peering link.
+- `state` (String) State of the peering connection.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `default` (String)
+- `delete` (String)
+- `read` (String)
+- `update` (String)
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import aiven_gcp_org_vpc_peering_connection.example ORGANIZATION_ID/ORGANIZATION_VPC_ID/GCP_PROJECT_ID/VPC_NAME
+```
diff --git a/docs/resources/organization_vpc.md b/docs/resources/organization_vpc.md
new file mode 100644
index 000000000..7f98c49ea
--- /dev/null
+++ b/docs/resources/organization_vpc.md
@@ -0,0 +1,66 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "aiven_organization_vpc Resource - terraform-provider-aiven"
+subcategory: ""
+description: |-
+ Creates and manages a VPC for an Aiven organization.
+ This resource is in the beta stage and may change without notice. Set
+ the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource.
+---
+
+# aiven_organization_vpc (Resource)
+
+Creates and manages a VPC for an Aiven organization.
+
+**This resource is in the beta stage and may change without notice.** Set
+the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource.
+
+## Example Usage
+
+```terraform
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "aws-eu-central-1"
+ network_cidr = "10.0.0.0/24"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `cloud_name` (String) The cloud provider and region where the service is hosted in the format `CLOUD_PROVIDER-REGION_NAME`. For example, `google-europe-west1` or `aws-us-east-2`. Changing this property forces recreation of the resource.
+- `network_cidr` (String) Network address range used by the VPC. For example, `192.168.0.0/24`.
+- `organization_id` (String) The ID of the organization.
+
+### Optional
+
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `create_time` (String) Time of creation of the VPC.
+- `id` (String) The ID of this resource.
+- `organization_vpc_id` (String) The ID of the Aiven Organization VPC.
+- `state` (String) State of the VPC. The possible values are `ACTIVE`, `APPROVED`, `DELETED` and `DELETING`.
+- `update_time` (String) Time of the last update of the VPC.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `default` (String)
+- `delete` (String)
+- `read` (String)
+- `update` (String)
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import aiven_organization_vpc.example ORGANIZATION_ID/ORGANIZATION_VPC_ID
+```
diff --git a/examples/resources/aiven_aws_org_vpc_peering_connection/import.sh b/examples/resources/aiven_aws_org_vpc_peering_connection/import.sh
new file mode 100644
index 000000000..b3064b075
--- /dev/null
+++ b/examples/resources/aiven_aws_org_vpc_peering_connection/import.sh
@@ -0,0 +1 @@
+terraform import aiven_aws_org_vpc_peering_connection.example ORGANIZATION_ID/ORGANIZATION_VPC_ID/AWS_ACCOUNT_ID/AWS_VPC_ID/AWS_REGION
\ No newline at end of file
diff --git a/examples/resources/aiven_aws_org_vpc_peering_connection/resource.tf b/examples/resources/aiven_aws_org_vpc_peering_connection/resource.tf
new file mode 100644
index 000000000..620c34648
--- /dev/null
+++ b/examples/resources/aiven_aws_org_vpc_peering_connection/resource.tf
@@ -0,0 +1,13 @@
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "aws-eu-central-1"
+ network_cidr = "10.0.0.0/24"
+}
+
+resource "aiven_aws_org_vpc_peering_connection" "example_peering" {
+ organization_id = aiven_organization_vpc.example_vpc.organization_id
+ organization_vpc_id = aiven_organization_vpc.example_vpc.organization_vpc_id
+ aws_account_id = var.aws_id
+ aws_vpc_id = "vpc-1a2b3c4d5e6f7g8h9"
+ aws_vpc_region = "aws-us-east-2"
+}
\ No newline at end of file
diff --git a/examples/resources/aiven_azure_org_vpc_peering_connection/import.sh b/examples/resources/aiven_azure_org_vpc_peering_connection/import.sh
new file mode 100644
index 000000000..8b942e75c
--- /dev/null
+++ b/examples/resources/aiven_azure_org_vpc_peering_connection/import.sh
@@ -0,0 +1 @@
+terraform import aiven_azure_org_vpc_peering_connection.example ORGANIZATION_ID/ORGANIZATION_VPC_ID/AZURE_SUBSCRIPTION_ID/VNET_NAME/RESOURCE_GROUP
\ No newline at end of file
diff --git a/examples/resources/aiven_azure_org_vpc_peering_connection/resource.tf b/examples/resources/aiven_azure_org_vpc_peering_connection/resource.tf
new file mode 100644
index 000000000..1cd073c4d
--- /dev/null
+++ b/examples/resources/aiven_azure_org_vpc_peering_connection/resource.tf
@@ -0,0 +1,15 @@
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "azure-germany-westcentral"
+ network_cidr = "10.0.0.0/24"
+}
+
+resource "aiven_azure_org_vpc_peering_connection" "example_peering" {
+ organization_id = aiven_organization_vpc.example_vpc.organization_id
+ organization_vpc_id = aiven_organization_vpc.example_vpc.organization_vpc_id
+ azure_subscription_id = "12345678-1234-1234-1234-123456789012"
+ vnet_name = "my-vnet"
+ peer_resource_group = "my-resource-group"
+ peer_azure_app_id = "87654321-4321-4321-4321-210987654321"
+ peer_azure_tenant_id = "11111111-2222-3333-4444-555555555555"
+}
\ No newline at end of file
diff --git a/examples/resources/aiven_gcp_org_vpc_peering_connection/import.sh b/examples/resources/aiven_gcp_org_vpc_peering_connection/import.sh
new file mode 100644
index 000000000..5e322b74b
--- /dev/null
+++ b/examples/resources/aiven_gcp_org_vpc_peering_connection/import.sh
@@ -0,0 +1 @@
+terraform import aiven_gcp_org_vpc_peering_connection.example ORGANIZATION_ID/ORGANIZATION_VPC_ID/GCP_PROJECT_ID/VPC_NAME
\ No newline at end of file
diff --git a/examples/resources/aiven_gcp_org_vpc_peering_connection/resource.tf b/examples/resources/aiven_gcp_org_vpc_peering_connection/resource.tf
new file mode 100644
index 000000000..97f7948b4
--- /dev/null
+++ b/examples/resources/aiven_gcp_org_vpc_peering_connection/resource.tf
@@ -0,0 +1,12 @@
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "google-europe-west10"
+ network_cidr = "10.0.0.0/24"
+}
+
+resource "aiven_gcp_org_vpc_peering_connection" "example" {
+ organization_id = aiven_organization_vpc.example_vpc.organization_id
+ organization_vpc_id = aiven_organization_vpc.example_vpc.organization_vpc_id
+ gcp_project_id = "my-gcp-project-123" # Your GCP project ID
+ peer_vpc = "my-vpc-network" # Your GCP VPC network name
+}
\ No newline at end of file
diff --git a/examples/resources/aiven_organization_vpc/import.sh b/examples/resources/aiven_organization_vpc/import.sh
new file mode 100644
index 000000000..a0b2f2f80
--- /dev/null
+++ b/examples/resources/aiven_organization_vpc/import.sh
@@ -0,0 +1 @@
+terraform import aiven_organization_vpc.example ORGANIZATION_ID/ORGANIZATION_VPC_ID
\ No newline at end of file
diff --git a/examples/resources/aiven_organization_vpc/resource.tf b/examples/resources/aiven_organization_vpc/resource.tf
new file mode 100644
index 000000000..50bea6014
--- /dev/null
+++ b/examples/resources/aiven_organization_vpc/resource.tf
@@ -0,0 +1,5 @@
+resource "aiven_organization_vpc" "example_vpc" {
+ organization_id = data.aiven_organization.example.id
+ cloud_name = "aws-eu-central-1"
+ network_cidr = "10.0.0.0/24"
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index b1c696e67..e16c45f2f 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/docker/go-units v0.5.0
github.com/ettle/strcase v0.2.0
github.com/google/go-cmp v0.6.0
+ github.com/google/uuid v1.6.0
github.com/gruntwork-io/terratest v0.48.2
github.com/hamba/avro/v2 v2.28.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go
index da84036a8..75a1aa7d5 100644
--- a/internal/acctest/acctest.go
+++ b/internal/acctest/acctest.go
@@ -230,3 +230,49 @@ func ResourceFromState(state *terraform.State, name string) (*terraform.Resource
return rs, nil
}
+
+// EnvVarCheckMode determines how missing environment variables are handled
+type EnvVarCheckMode int
+
+const (
+ // MustBeSet fails the test if any required env vars are missing
+ MustBeSet EnvVarCheckMode = iota
+ // SkipIfMissing skips the test if any required env vars are missing
+ SkipIfMissing
+)
+
+func CheckEnvVars(t *testing.T, mode EnvVarCheckMode, vars ...string) map[string]string {
+ t.Helper()
+
+ values := make(map[string]string)
+ missingVars := make([]string, 0)
+
+ for _, v := range vars {
+ val, ok := os.LookupEnv(v)
+ if !ok {
+ missingVars = append(missingVars, v)
+ continue
+ }
+ values[v] = val
+ }
+
+ if len(missingVars) > 0 {
+ msg := fmt.Sprintf("required environment variables not set: %s", strings.Join(missingVars, ", "))
+ switch mode {
+ case MustBeSet:
+ t.Fatal(msg)
+ case SkipIfMissing:
+ t.Skip(msg)
+ }
+ }
+
+ return values
+}
+
+func MustHaveEnvVars(t *testing.T, vars ...string) map[string]string {
+ return CheckEnvVars(t, MustBeSet, vars...)
+}
+
+func SkipIfEnvVarsNotSet(t *testing.T, vars ...string) map[string]string {
+ return CheckEnvVars(t, SkipIfMissing, vars...)
+}
diff --git a/internal/acctest/template.go b/internal/acctest/template.go
index 1cca67355..1d48aba58 100644
--- a/internal/acctest/template.go
+++ b/internal/acctest/template.go
@@ -221,3 +221,16 @@ func (b *CompositionBuilder) MustRender(t testing.TB) string {
}
return result
}
+
+type TemplateValue struct {
+ Value string
+ IsLiteral bool
+}
+
+func Literal(v string) TemplateValue {
+ return TemplateValue{Value: v, IsLiteral: true}
+}
+
+func Reference(v string) TemplateValue {
+ return TemplateValue{Value: v, IsLiteral: false}
+}
diff --git a/internal/common/client.go b/internal/common/client.go
index 241a67e31..b47c87f66 100644
--- a/internal/common/client.go
+++ b/internal/common/client.go
@@ -71,6 +71,17 @@ func WithGenClient(handler CrudHandler) func(context.Context, *schema.ResourceDa
}
}
+// WithGenClientDiag wraps the CRUD handlers and runs with avngen.Client, but returns diag.Diagnostics instead of error
+func WithGenClientDiag(f func(context.Context, *schema.ResourceData, avngen.Client) diag.Diagnostics) func(
+ context.Context,
+ *schema.ResourceData,
+ any,
+) diag.Diagnostics {
+ return func(ctx context.Context, d *schema.ResourceData, _ any) diag.Diagnostics {
+ return f(ctx, d, genClientCache)
+ }
+}
+
type ClientOpt func(o *clientOpts)
type clientOpts struct {
token string
diff --git a/internal/schemautil/schemautil.go b/internal/schemautil/schemautil.go
index 3766e6116..0daca34b7 100644
--- a/internal/schemautil/schemautil.go
+++ b/internal/schemautil/schemautil.go
@@ -246,6 +246,15 @@ func SplitResourceID4(resourceID string) (string, string, string, string, error)
return parts[0], parts[1], parts[2], parts[3], nil
}
+func SplitResourceID5(resourceID string) (string, string, string, string, string, error) {
+ parts, err := SplitResourceID(resourceID, 5)
+ if err != nil {
+ return "", "", "", "", "", err
+ }
+
+ return parts[0], parts[1], parts[2], parts[3], parts[4], nil
+}
+
func FlattenToString[T any](a []T) []string {
r := make([]string, len(a))
for i, v := range a {
diff --git a/internal/sdkprovider/provider/provider.go b/internal/sdkprovider/provider/provider.go
index a5541b5eb..60fe7d42d 100644
--- a/internal/sdkprovider/provider/provider.go
+++ b/internal/sdkprovider/provider/provider.go
@@ -109,14 +109,18 @@ func Provider(version string) (*schema.Provider, error) {
"aiven_organization_project": project.DatasourceOrganizationProject(),
// vpc
- "aiven_aws_privatelink": vpc.DatasourceAWSPrivatelink(),
- "aiven_aws_vpc_peering_connection": vpc.DatasourceAWSVPCPeeringConnection(),
- "aiven_azure_privatelink": vpc.DatasourceAzurePrivatelink(),
- "aiven_azure_vpc_peering_connection": vpc.DatasourceAzureVPCPeeringConnection(),
- "aiven_gcp_privatelink": vpc.DatasourceGCPPrivatelink(),
- "aiven_gcp_vpc_peering_connection": vpc.DatasourceGCPVPCPeeringConnection(),
- "aiven_project_vpc": vpc.DatasourceProjectVPC(),
- "aiven_transit_gateway_vpc_attachment": vpc.DatasourceTransitGatewayVPCAttachment(),
+ "aiven_aws_privatelink": vpc.DatasourceAWSPrivatelink(),
+ "aiven_aws_vpc_peering_connection": vpc.DatasourceAWSVPCPeeringConnection(),
+ "aiven_aws_org_vpc_peering_connection": vpc.DatasourceAWSOrgVPCPeeringConnection(),
+ "aiven_azure_privatelink": vpc.DatasourceAzurePrivatelink(),
+ "aiven_azure_vpc_peering_connection": vpc.DatasourceAzureVPCPeeringConnection(),
+ "aiven_azure_org_vpc_peering_connection": vpc.DatasourceAzureOrgVPCPeeringConnection(),
+ "aiven_gcp_privatelink": vpc.DatasourceGCPPrivatelink(),
+ "aiven_gcp_vpc_peering_connection": vpc.DatasourceGCPVPCPeeringConnection(),
+ "aiven_gcp_org_vpc_peering_connection": vpc.DatasourceGCPOrgVPCPeeringConnection(),
+ "aiven_project_vpc": vpc.DatasourceProjectVPC(),
+ "aiven_transit_gateway_vpc_attachment": vpc.DatasourceTransitGatewayVPCAttachment(),
+ "aiven_organization_vpc": vpc.DataSourceOrganizationVPC(),
// service integrations
"aiven_service_integration": serviceintegration.DatasourceServiceIntegration(),
@@ -227,14 +231,18 @@ func Provider(version string) (*schema.Provider, error) {
// vpc
"aiven_aws_privatelink": vpc.ResourceAWSPrivatelink(),
"aiven_aws_vpc_peering_connection": vpc.ResourceAWSVPCPeeringConnection(),
+ "aiven_aws_org_vpc_peering_connection": vpc.ResourceAWSOrgVPCPeeringConnection(),
"aiven_azure_privatelink": vpc.ResourceAzurePrivatelink(),
"aiven_azure_privatelink_connection_approval": vpc.ResourceAzurePrivatelinkConnectionApproval(),
"aiven_azure_vpc_peering_connection": vpc.ResourceAzureVPCPeeringConnection(),
+ "aiven_azure_org_vpc_peering_connection": vpc.ResourceAzureOrgVPCPeeringConnection(),
"aiven_gcp_privatelink": vpc.ResourceGCPPrivatelink(),
"aiven_gcp_privatelink_connection_approval": vpc.ResourceGCPPrivatelinkConnectionApproval(),
"aiven_gcp_vpc_peering_connection": vpc.ResourceGCPVPCPeeringConnection(),
+ "aiven_gcp_org_vpc_peering_connection": vpc.ResourceGCPOrgVPCPeeringConnection(),
"aiven_project_vpc": vpc.ResourceProjectVPC(),
"aiven_transit_gateway_vpc_attachment": vpc.ResourceTransitGatewayVPCAttachment(),
+ "aiven_organization_vpc": vpc.ResourceOrganizationVPC(),
// service integrations
"aiven_service_integration": serviceintegration.ResourceServiceIntegration(),
@@ -304,6 +312,10 @@ func Provider(version string) (*schema.Provider, error) {
"aiven_flink_jar_application_version",
"aiven_flink_jar_application_deployment",
"aiven_organization_project",
+ "aiven_organization_vpc",
+ "aiven_aws_org_vpc_peering_connection",
+ "aiven_gcp_org_vpc_peering_connection",
+ "aiven_azure_org_vpc_peering_connection",
}
betaDataSources := []string{
@@ -312,6 +324,10 @@ func Provider(version string) (*schema.Provider, error) {
"aiven_alloydbomni_database",
"aiven_organization_user_list",
"aiven_organization_project",
+ "aiven_organization_vpc",
+ "aiven_aws_org_vpc_peering_connection",
+ "aiven_gcp_org_vpc_peering_connection",
+ "aiven_azure_org_vpc_peering_connection",
}
missing := append(
diff --git a/internal/sdkprovider/service/project/organization_project_test.go b/internal/sdkprovider/service/project/organization_project_test.go
index f8505aabf..8197a7d42 100644
--- a/internal/sdkprovider/service/project/organization_project_test.go
+++ b/internal/sdkprovider/service/project/organization_project_test.go
@@ -33,47 +33,47 @@ func TestAccAivenOrganizationProject(t *testing.T) {
// test creating project with all possible fields
{
Config: fmt.Sprintf(`
- resource "aiven_organization" "foo" {
- name = "test-acc-org-%[1]s"
- }
-
- resource "aiven_billing_group" "foo" {
- name = "test-acc-bg-%[1]s"
- }
-
- resource "aiven_organizational_unit" "foo" {
- name = "test-acc-unit-%[1]s"
- parent_id = aiven_organization.foo.id
- }
-
- resource "aiven_organization_project" "foo" {
- project_id = "%[2]s"
-
- organization_id = aiven_organization.foo.id
- billing_group_id = aiven_billing_group.foo.id
- parent_id = aiven_organizational_unit.foo.id
- technical_emails = ["john.doe+1@gmail.com", "john.doe+2@gmail.com"]
-
- tag {
- key = "key1"
- value = "value1"
- }
-
- tag {
- key = "key2"
- value = "value2"
- }
-
- tag {
- key = "key3"
- value = "value3"
- }
- }
-
- data "aiven_organization_project" "ds_test" {
- project_id = aiven_organization_project.foo.project_id
- organization_id = aiven_organization_project.foo.organization_id
- }
+resource "aiven_organization" "foo" {
+ name = "test-acc-org-%[1]s"
+}
+
+resource "aiven_billing_group" "foo" {
+ name = "test-acc-bg-%[1]s"
+}
+
+resource "aiven_organizational_unit" "foo" {
+ name = "test-acc-unit-%[1]s"
+ parent_id = aiven_organization.foo.id
+}
+
+resource "aiven_organization_project" "foo" {
+ project_id = "%[2]s"
+
+ organization_id = aiven_organization.foo.id
+ billing_group_id = aiven_billing_group.foo.id
+ parent_id = aiven_organizational_unit.foo.id
+ technical_emails = ["john.doe+1@gmail.com", "john.doe+2@gmail.com"]
+
+ tag {
+ key = "key1"
+ value = "value1"
+ }
+
+ tag {
+ key = "key2"
+ value = "value2"
+ }
+
+ tag {
+ key = "key3"
+ value = "value3"
+ }
+}
+
+data "aiven_organization_project" "ds_test" {
+ project_id = aiven_organization_project.foo.project_id
+ organization_id = aiven_organization_project.foo.organization_id
+}
`, rName, projectID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "project_id", projectID),
@@ -117,44 +117,42 @@ func TestAccAivenOrganizationProject(t *testing.T) {
},
// test resource update
{
- Config: fmt.Sprintf(
- `
- resource "aiven_organization" "foo" {
- name = "test-acc-org-%[1]s"
- }
-
- resource "aiven_billing_group" "foo" {
- name = "test-acc-bg-%[1]s"
- }
-
- resource "aiven_organizational_unit" "foo" {
- name = "test-acc-unit-%[1]s"
- parent_id = aiven_organization.foo.id
- }
-
- resource "aiven_organization_project" "foo" {
- project_id = "%[2]s" #updating project_id without changing other billing_group_id would fail in this scenario
-
- organization_id = aiven_organization.foo.id # should not change
- billing_group_id = aiven_billing_group.foo.id
- parent_id = aiven_organizational_unit.foo.id
- technical_emails = ["john.doe+3@gmail.com", "john.doe+2@gmail.com", "john.doe+4@gmail.com"] #update emails
-
- tag { #update tags
- key = "key1"
- value = "value1"
- }
- tag {
- key = "key2"
- value = "value2-new"
- }
- tag {
- key = "key4"
- value = "value4"
- }
- }
- `,
- rName,
+ Config: fmt.Sprintf(`
+resource "aiven_organization" "foo" {
+ name = "test-acc-org-%[1]s"
+}
+
+resource "aiven_billing_group" "foo" {
+ name = "test-acc-bg-%[1]s"
+}
+
+resource "aiven_organizational_unit" "foo" {
+ name = "test-acc-unit-%[1]s"
+ parent_id = aiven_organization.foo.id
+}
+
+resource "aiven_organization_project" "foo" {
+ project_id = "%[2]s" #updating project_id without changing other billing_group_id would fail in this scenario
+
+ organization_id = aiven_organization.foo.id # should not change
+ billing_group_id = aiven_billing_group.foo.id
+ parent_id = aiven_organizational_unit.foo.id
+ technical_emails = ["john.doe+3@gmail.com", "john.doe+2@gmail.com", "john.doe+4@gmail.com"] #update emails
+
+ tag { #update tags
+ key = "key1"
+ value = "value1"
+ }
+ tag {
+ key = "key2"
+ value = "value2-new"
+ }
+ tag {
+ key = "key4"
+ value = "value4"
+ }
+}
+ `, rName,
projectID,
),
ConfigPlanChecks: resource.ConfigPlanChecks{
diff --git a/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection.go
new file mode 100644
index 000000000..55815743f
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection.go
@@ -0,0 +1,211 @@
+package vpc
+
+import (
+ "context"
+ "fmt"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/plugin/util"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig"
+)
+
+var aivenAWSOrgVPCPeeringConnectionSchema = map[string]*schema.Schema{
+ "organization_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Identifier of the organization.",
+ },
+ "organization_vpc_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Identifier of the organization VPC.",
+ },
+ "aws_account_id": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("AWS account ID.").ForceNew().Build(),
+ },
+ "aws_vpc_id": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("AWS VPC ID.").ForceNew().Build(),
+ },
+ "aws_vpc_region": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The AWS region of the peered VPC. For example, `eu-central-1`.").Build(),
+ },
+ "peering_connection_id": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The ID of the peering connection.").Build(),
+ },
+ "aws_vpc_peering_connection_id": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: "The ID of the AWS VPC peering connection.",
+ },
+ "state": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("State of the peering connection.").PossibleValuesString(organizationvpc.VpcPeeringConnectionStateTypeChoices()...).Build(),
+ },
+}
+
+func ResourceAWSOrgVPCPeeringConnection() *schema.Resource {
+ return &schema.Resource{
+ Description: "Creates and manages an AWS VPC peering connection with an Aiven Organization VPC.",
+ CreateContext: common.WithGenClientDiag(resourceAWSOrgVPCPeeringConnectionCreate),
+ ReadContext: common.WithGenClientDiag(resourceAWSOrgVPCPeeringConnectionRead),
+ DeleteContext: common.WithGenClientDiag(resourceAWSOrgVPCPeeringConnectionDelete),
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Timeouts: schemautil.DefaultResourceTimeouts(),
+
+ Schema: aivenAWSOrgVPCPeeringConnectionSchema,
+ }
+}
+
+func resourceAWSOrgVPCPeeringConnectionCreate(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ var (
+ orgID = d.Get("organization_id").(string)
+ vpcID = d.Get("organization_vpc_id").(string)
+ awsAccountID = d.Get("aws_account_id").(string)
+ awsVPCId = d.Get("aws_vpc_id").(string)
+ awsRegion = d.Get("aws_vpc_region").(string)
+
+ req = organizationvpc.OrganizationVpcPeeringConnectionCreateIn{
+ PeerRegion: util.ToPtr(awsRegion),
+ PeerVpc: awsVPCId,
+ PeerCloudAccount: awsAccountID,
+ }
+ )
+
+ pCon, err := createPeeringConnection(ctx, orgID, vpcID, client, d, req)
+ if err != nil {
+ return diag.Errorf("Error creating VPC peering connection: %s", err)
+ }
+
+ diags := getDiagnosticsFromState(newOrganizationVPCPeeringState(pCon))
+
+ d.SetId(schemautil.BuildResourceID(orgID, vpcID, awsAccountID, awsVPCId, awsRegion))
+
+ // in case of an error delete VPC peering connection
+ if diags.HasError() {
+ deleteDiags := resourceAzureOrgVPCPeeringConnectionDelete(ctx, d, client)
+ d.SetId("") // Clear the ID after delete
+
+ return append(diags, deleteDiags...)
+ }
+
+ return append(diags, resourceAWSOrgVPCPeeringConnectionRead(ctx, d, client)...)
+}
+
+func resourceAWSOrgVPCPeeringConnectionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ orgID, vpcID, awsAccountID, awsVpcID, awsRegion, err := schemautil.SplitResourceID5(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ vpc, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return diag.FromErr(schemautil.ResourceReadHandleNotFound(err, d))
+ }
+
+ return diag.Errorf("failed to get VPC with ID %q: %s", vpcID, err)
+ }
+
+ pc := lookupAWSPeeringConnection(vpc, awsAccountID, awsVpcID, awsRegion)
+ if pc == nil {
+ d.SetId("") // Clear the ID as the resource is not found
+
+ return diag.FromErr(fmt.Errorf("VPC peering connection not found"))
+ }
+
+ if err = d.Set("organization_id", vpc.OrganizationId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("organization_vpc_id", vpc.OrganizationVpcId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("peering_connection_id", *pc.PeeringConnectionId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("aws_account_id", pc.PeerCloudAccount); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("aws_vpc_id", pc.PeerVpc); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("aws_vpc_region", *pc.PeerRegion); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("aws_vpc_peering_connection_id", pc.StateInfo.AwsVpcPeeringConnectionId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("state", string(pc.State)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
+
+func resourceAWSOrgVPCPeeringConnectionDelete(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ orgID, vpcID, awsAccountID, awsVpcID, awsRegion, err := schemautil.SplitResourceID5(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ vpc, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return nil // consider already deleted
+ }
+
+ return diag.Errorf("failed to get VPC with ID %q: %s", vpcID, err)
+ }
+
+ if err = deletePeeringConnection(
+ ctx,
+ orgID,
+ vpcID,
+ client,
+ d,
+ lookupAWSPeeringConnection(vpc, awsAccountID, awsVpcID, awsRegion),
+ ); err != nil {
+ return diag.Errorf("Error deleting Azure Aiven VPC Peering Connection: %s", err)
+ }
+
+ return nil
+}
+
+func lookupAWSPeeringConnection(
+ vpc *organizationvpc.OrganizationVpcGetOut,
+ awsAccountID, awsVpcID, awsRegion string,
+) *organizationvpc.OrganizationVpcGetPeeringConnectionOut {
+ for _, pCon := range vpc.PeeringConnections {
+ if pCon.PeerCloudAccount == awsAccountID &&
+ pCon.PeerVpc == awsVpcID &&
+ pCon.PeerRegion != nil &&
+ *pCon.PeerRegion == awsRegion &&
+ pCon.PeeringConnectionId != nil {
+
+ return &pCon
+ }
+ }
+
+ return nil
+}
diff --git a/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection_data_source.go b/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection_data_source.go
new file mode 100644
index 000000000..5eb38d2e8
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection_data_source.go
@@ -0,0 +1,35 @@
+package vpc
+
+import (
+ "context"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+func DatasourceAWSOrgVPCPeeringConnection() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: common.WithGenClientDiag(dataSourceAWSOrgVPCPeeringConnectionRead),
+ Description: "Gets information about an AWS VPC peering connection.",
+ Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenAWSOrgVPCPeeringConnectionSchema,
+ "organization_id", "organization_vpc_id", "aws_account_id", "aws_vpc_id", "aws_vpc_region"),
+ }
+}
+
+func dataSourceAWSOrgVPCPeeringConnectionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ var (
+ orgID = d.Get("organization_id").(string)
+ orgVpcID = d.Get("organization_vpc_id").(string)
+ awsAccountID = d.Get("aws_account_id").(string)
+ awsVpcID = d.Get("aws_vpc_id").(string)
+ awsRegion = d.Get("aws_vpc_region").(string)
+ )
+
+ d.SetId(schemautil.BuildResourceID(orgID, orgVpcID, awsAccountID, awsVpcID, awsRegion))
+
+ return resourceAWSOrgVPCPeeringConnectionRead(ctx, d, client)
+}
diff --git a/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection_test.go b/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection_test.go
new file mode 100644
index 000000000..6cca0577f
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/aws_org_vpc_peering_connection_test.go
@@ -0,0 +1,320 @@
+package vpc_test
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+
+ acc "github.com/aiven/terraform-provider-aiven/internal/acctest"
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+const (
+ awsOrgVPCPeeringResource = "aiven_aws_org_vpc_peering_connection"
+)
+
+// TestAccAivenAWSOrgVPCPeeringConnection tests the AWS VPC peering connection resource functionality.
+// Since creating a real AWS VPC peering connection in CI is not feasible now, this test:
+// 1. Sets up a test environment with a fake AWS account ID and VPC ID
+// 2. Attempts to create an Aiven VPC and a peering connection
+// 3. Validates that the creation fails with the expected error due to invalid AWS credentials
+func TestAccAivenAWSOrgVPCPeeringConnection(t *testing.T) {
+ var (
+ orgName = acc.SkipIfEnvVarsNotSet(t, "AIVEN_ORGANIZATION_NAME")["AIVEN_ORGANIZATION_NAME"]
+ registry = preSetAwsOrgVPCPeeringTemplates(t)
+ newComposition = func() *acc.CompositionBuilder {
+ return registry.NewCompositionBuilder().
+ Add("organization_data", map[string]any{
+ "organization_name": orgName})
+ }
+
+ awsAccountID = "123456789012" // Fake AWS account ID
+ awsVpcID = "vpc-1a1a111a111a11a11" // Fake AWS VPC ID
+ awsRegion = "eu-west-2"
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckAWSOrgVPCPeeringResourceDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: newComposition().
+ Add(organizationVPCResource, map[string]any{
+ "resource_name": "test_org_vpc",
+ "cloud_name": fmt.Sprintf("aws-%s", awsRegion),
+ "network_cidr": "10.0.0.0/24",
+ }).
+ Add(awsOrgVPCPeeringResource, map[string]any{
+ "resource_name": "test_org_vpc_peering",
+ "organization_id": acc.Reference("data.aiven_organization.foo.id"),
+ "organization_vpc_id": acc.Reference("aiven_organization_vpc.test_org_vpc.organization_vpc_id"),
+ "aws_account_id": acc.Literal(awsAccountID),
+ "aws_vpc_id": acc.Literal(awsVpcID),
+ "aws_vpc_region": awsRegion,
+ }).MustRender(t),
+ ExpectError: regexp.MustCompile(`VPC peering connection cannot be created`), // Expected error due to invalid AWS account ID
+ },
+ },
+ })
+}
+
+// TestAccAivenAWSOrgVPCPeeringConnectionFull tests the complete AWS VPC peering connection workflow
+// with real AWS resources. This test:
+// 1. Creates an AWS VPC and route table
+// 2. Creates an Aiven Organization VPC
+// 3. Establishes VPC peering between AWS and Aiven VPCs
+// 4. Accepts the peering connection on AWS side
+// 5. Sets up routing for the peered VPCs
+//
+// Note: The test will be skipped in CI environments since it requires real AWS credentials
+// and resources. This test is meant for local development and verification for now.
+// Prerequisites:
+// - Valid AWS credentials with permissions to create/delete VPC resources
+// - Proper AWS profile configuration (can be set via AWS_PROFILE env var)
+// - Required permissions: VPC creation/deletion, VPC peering, route table management
+func TestAccAivenAWSOrgVPCPeeringConnectionFull(t *testing.T) {
+ var envVars = acc.SkipIfEnvVarsNotSet(
+ t,
+ "AIVEN_ORGANIZATION_NAME",
+ "AWS_ACCESS_KEY_ID",
+ "AWS_SECRET_ACCESS_KEY",
+ "AWS_SESSION_TOKEN",
+ )
+
+ var (
+ orgName = envVars["AIVEN_ORGANIZATION_NAME"]
+ awsRegion = "eu-central-1"
+ registry = preSetAwsOrgVPCPeeringTemplates(t)
+ newComposition = func() *acc.CompositionBuilder {
+ return registry.NewCompositionBuilder().
+ Add("aws_provider", map[string]any{
+ "aws_region": awsRegion}).
+ Add("organization_data", map[string]any{
+ "organization_name": orgName})
+ }
+
+ resourceName = fmt.Sprintf("%s.%s", awsOrgVPCPeeringResource, "test_peering")
+
+ randName = acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
+ serviceName = fmt.Sprintf("test-acc-%s", randName)
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ CheckDestroy: testAccCheckAWSOrgVPCPeeringResourceDestroy,
+ ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories,
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "aws": {
+ Source: "hashicorp/aws",
+ VersionConstraint: "=5.84.0",
+ },
+ },
+ Steps: []resource.TestStep{
+ {
+ Config: newComposition().
+ Add("aws_vpc", map[string]any{
+ "resource_name": "example",
+ "cidr_block": "172.16.0.0/16",
+ "vpc_name": fmt.Sprintf("%s-vpc", serviceName),
+ }).
+ Add("aws_route_table", map[string]any{
+ "resource_name": "example",
+ "vpc_id": "aws_vpc.example.id",
+ "route_table_name": fmt.Sprintf("%s-route-table", serviceName),
+ }).
+ Add(organizationVPCResource, map[string]any{
+ "resource_name": "example",
+ "cloud_name": fmt.Sprintf("aws-%s", awsRegion),
+ "network_cidr": "10.0.0.0/24",
+ }).
+ Add(awsOrgVPCPeeringResource, map[string]any{
+ "resource_name": "test_peering",
+ "organization_id": acc.Reference("data.aiven_organization.foo.id"),
+ "organization_vpc_id": acc.Reference("aiven_organization_vpc.example.organization_vpc_id"),
+ "aws_account_id": acc.Reference("aws_vpc.example.owner_id"),
+ "aws_vpc_id": acc.Reference("aws_vpc.example.id"),
+ "aws_vpc_region": awsRegion,
+ }).
+ Add("peering_datasource", map[string]any{
+ "resource_name": "test_peering",
+ "organization_id": acc.Reference("data.aiven_organization.foo.id"),
+ "organization_vpc_id": acc.Reference("aiven_organization_vpc.example.organization_vpc_id"),
+ "aws_account_id": acc.Reference("aws_vpc.example.owner_id"),
+ "aws_vpc_id": acc.Reference("aws_vpc.example.id"),
+ "aws_vpc_region": awsRegion,
+ }).
+ Add("aws_vpc_peering_accepter", map[string]any{
+ "resource_name": "example",
+ "peering_connection_id": "aiven_aws_org_vpc_peering_connection.test_peering.aws_vpc_peering_connection_id",
+ "peering_name": fmt.Sprintf("%s-peering-accepter", serviceName),
+ }).
+ Add("aws_route", map[string]any{
+ "resource_name": "aiven_vpc_route",
+ "route_table_id": "aws_route_table.example.id",
+ "destination_cidr": "aiven_organization_vpc.example.network_cidr",
+ "peering_connection_id": "aws_vpc_peering_connection_accepter.example.vpc_peering_connection_id",
+ }).MustRender(t),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrPair(resourceName, "organization_id", "data.aiven_organization.foo", "id"),
+ resource.TestCheckResourceAttrPair(resourceName, "organization_vpc_id", "aiven_organization_vpc.example", "organization_vpc_id"),
+ resource.TestCheckResourceAttrPair(resourceName, "aws_account_id", "aws_vpc.example", "owner_id"),
+ resource.TestCheckResourceAttrPair(resourceName, "aws_vpc_id", "aws_vpc.example", "id"),
+ resource.TestCheckResourceAttrPair(resourceName, "organization_id", "data.aiven_organization.foo", "id"),
+ resource.TestCheckResourceAttr(resourceName, "aws_vpc_region", awsRegion),
+ resource.TestCheckResourceAttrSet(resourceName, "peering_connection_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "aws_vpc_peering_connection_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "state"),
+
+ resource.TestCheckResourceAttrPair(fmt.Sprintf("data.%s.%s", awsOrgVPCPeeringResource, "test_peering"), "organization_id", "data.aiven_organization.foo", "id"),
+ resource.TestCheckResourceAttrPair(fmt.Sprintf("data.%s.%s", awsOrgVPCPeeringResource, "test_peering"), "organization_vpc_id", "aiven_organization_vpc.example", "organization_vpc_id"),
+ ),
+ },
+ {
+ // importing the resource
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func testAccCheckAWSOrgVPCPeeringResourceDestroy(s *terraform.State) error {
+ ctx := context.Background()
+
+ c, err := acc.GetTestGenAivenClient()
+ if err != nil {
+ return fmt.Errorf("error initializing Aiven client: %w", err)
+ }
+
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != awsOrgVPCPeeringResource {
+ continue
+ }
+
+ orgID, orgVpcID, awsAccountID, awsVpcID, awsRegion, err := schemautil.SplitResourceID5(rs.Primary.ID)
+ if err != nil {
+ return fmt.Errorf("error splitting resource with ID: %q - %w", rs.Primary.ID, err)
+ }
+
+ orgVPC, err := c.OrganizationVpcGet(ctx, orgID, orgVpcID)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error fetching VPC (%q): %w", orgVpcID, err)
+ }
+
+ if orgVPC == nil {
+ return nil // Peering connection was deleted with the VPC
+ }
+
+ var pc *organizationvpc.OrganizationVpcGetPeeringConnectionOut
+ for _, pCon := range orgVPC.PeeringConnections {
+ if pCon.PeerCloudAccount == awsAccountID &&
+ pCon.PeerVpc == awsVpcID &&
+ pCon.PeerRegion != nil &&
+ *pCon.PeerRegion == awsRegion &&
+ pCon.PeeringConnectionId != nil {
+ pc = &pCon
+
+ break
+ }
+ }
+
+ if pc != nil {
+ return fmt.Errorf("peering connection %q still exists", *pc.PeeringConnectionId)
+ }
+ }
+
+ return nil
+}
+
+func preSetAwsOrgVPCPeeringTemplates(t *testing.T) *acc.TemplateRegistry {
+ t.Helper()
+
+ registry := acc.NewTemplateRegistry(awsOrgVPCPeeringResource)
+
+ registry.MustAddTemplate(t, "organization_data", `
+data "aiven_organization" "foo" {
+ name = "{{ .organization_name }}"
+}`)
+
+ registry.MustAddTemplate(t, "aws_provider", `
+provider "aws" {
+ region = "{{ .aws_region }}"
+}`)
+
+ registry.MustAddTemplate(t, organizationVPCResource, `
+resource "aiven_organization_vpc" "{{ .resource_name }}" {
+ organization_id = data.aiven_organization.foo.id
+ cloud_name = "{{ .cloud_name }}"
+ network_cidr = "{{ .network_cidr }}"
+}`)
+
+ registry.MustAddTemplate(t, awsOrgVPCPeeringResource, `
+resource "aiven_aws_org_vpc_peering_connection" "{{ .resource_name }}" {
+ organization_id = {{ if .organization_id.IsLiteral }}"{{ .organization_id.Value }}"{{ else }}{{ .organization_id.Value }}{{ end }}
+ organization_vpc_id = {{ if .organization_vpc_id.IsLiteral }}"{{ .organization_vpc_id.Value }}"{{ else }}{{ .organization_vpc_id.Value }}{{ end }}
+ aws_account_id = {{ if .aws_account_id.IsLiteral }}"{{ .aws_account_id.Value }}"{{ else }}{{ .aws_account_id.Value }}{{ end }}
+ aws_vpc_id = {{ if .aws_vpc_id.IsLiteral }}"{{ .aws_vpc_id.Value }}"{{ else }}{{ .aws_vpc_id.Value }}{{ end }}
+ aws_vpc_region = "{{ .aws_vpc_region }}"
+}`)
+
+ registry.MustAddTemplate(t, "peering_datasource", `
+data "aiven_aws_org_vpc_peering_connection" "{{ .resource_name }}" {
+ organization_id = {{ if .organization_id.IsLiteral }}"{{ .organization_id.Value }}"{{ else }}{{ .organization_id.Value }}{{ end }}
+ organization_vpc_id = {{ if .organization_vpc_id.IsLiteral }}"{{ .organization_vpc_id.Value }}"{{ else }}{{ .organization_vpc_id.Value }}{{ end }}
+ aws_account_id = {{ if .aws_account_id.IsLiteral }}"{{ .aws_account_id.Value }}"{{ else }}{{ .aws_account_id.Value }}{{ end }}
+ aws_vpc_id = {{ if .aws_vpc_id.IsLiteral }}"{{ .aws_vpc_id.Value }}"{{ else }}{{ .aws_vpc_id.Value }}{{ end }}
+ aws_vpc_region = "{{ .aws_vpc_region }}"
+}`)
+
+ // AWS VPC Resource
+ registry.MustAddTemplate(t, "aws_vpc", `
+resource "aws_vpc" "{{ .resource_name }}" {
+ cidr_block = "{{ .cidr_block }}"
+ enable_dns_hostnames = true
+ enable_dns_support = true
+
+ tags = {
+ Name = "{{ .vpc_name }}"
+ }
+}`)
+
+ // AWS Route Table
+ registry.MustAddTemplate(t, "aws_route_table", `
+resource "aws_route_table" "{{ .resource_name }}" {
+ vpc_id = {{ .vpc_id }}
+
+ tags = {
+ Name = "{{ .route_table_name }}"
+ }
+}`)
+
+ // AWS VPC Peering Connection Accepter
+ registry.MustAddTemplate(t, "aws_vpc_peering_accepter", `
+resource "aws_vpc_peering_connection_accepter" "{{ .resource_name }}" {
+ vpc_peering_connection_id = {{ .peering_connection_id }}
+ auto_accept = true
+
+ tags = {
+ Name = "{{ .peering_name }}"
+ }
+}`)
+
+ // AWS Route for Aiven VPC CIDR
+ registry.MustAddTemplate(t, "aws_route", `
+resource "aws_route" "{{ .resource_name }}" {
+ route_table_id = {{ .route_table_id }}
+ destination_cidr_block = {{ .destination_cidr }}
+ vpc_peering_connection_id = {{ .peering_connection_id }}
+}`)
+
+ return registry
+}
diff --git a/internal/sdkprovider/service/vpc/aws_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/aws_vpc_peering_connection.go
index 592a2c913..7163a5c22 100644
--- a/internal/sdkprovider/service/vpc/aws_vpc_peering_connection.go
+++ b/internal/sdkprovider/service/vpc/aws_vpc_peering_connection.go
@@ -159,7 +159,7 @@ func resourceAWSVPCPeeringConnectionCreate(ctx context.Context, d *schema.Resour
}
pc = res.(*aiven.VPCPeeringConnection)
- diags := getDiagnosticsFromState(pc)
+ diags := getDiagnosticsFromState(newAivenVPCPeeringState(pc))
d.SetId(schemautil.BuildResourceID(projectName, vpcID, pc.PeerCloudAccount, pc.PeerVPC, *pc.PeerRegion))
diff --git a/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection.go
new file mode 100644
index 000000000..d58fc6f64
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection.go
@@ -0,0 +1,223 @@
+package vpc
+
+import (
+ "context"
+ "fmt"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/plugin/util"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig"
+)
+
+var aivenAzureOrgVPCPeeringConnectionSchema = map[string]*schema.Schema{
+ "organization_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Identifier of the organization.",
+ },
+ "organization_vpc_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Identifier of the organization VPC.",
+ },
+ "azure_subscription_id": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The ID of the Azure subscription in UUID4 format.").ForceNew().Build(),
+ },
+ "vnet_name": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The name of the Azure VNet.").ForceNew().Build(),
+ },
+ "peer_resource_group": {
+ Required: true,
+ ForceNew: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The name of the Azure resource group associated with the VNet.").ForceNew().Build(),
+ },
+ "peer_azure_app_id": {
+ Required: true,
+ ForceNew: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The ID of the Azure app that is allowed to create a peering to the Azure Virtual Network (VNet) in UUID4 format.").ForceNew().Build(),
+ },
+ "peer_azure_tenant_id": {
+ Required: true,
+ ForceNew: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("The Azure tenant ID in UUID4 format.").ForceNew().Build(),
+ },
+ "state": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: "State of the peering connection",
+ },
+ "peering_connection_id": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: "The ID of the cloud provider for the peering connection.",
+ },
+}
+
+func ResourceAzureOrgVPCPeeringConnection() *schema.Resource {
+ return &schema.Resource{
+ Description: "Creates and manages an Azure VPC peering connection with an Aiven VPC.",
+ CreateContext: common.WithGenClientDiag(resourceAzureOrgVPCPeeringConnectionCreate),
+ ReadContext: common.WithGenClientDiag(resourceAzureOrgVPCPeeringConnectionRead),
+ DeleteContext: common.WithGenClientDiag(resourceAzureOrgVPCPeeringConnectionDelete),
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Timeouts: schemautil.DefaultResourceTimeouts(),
+
+ Schema: aivenAzureOrgVPCPeeringConnectionSchema,
+ }
+}
+
+func resourceAzureOrgVPCPeeringConnectionCreate(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ var (
+ orgID = d.Get("organization_id").(string)
+ vpcID = d.Get("organization_vpc_id").(string)
+ azureSubscriptionID = d.Get("azure_subscription_id").(string)
+ vnetName = d.Get("vnet_name").(string)
+ appID = d.Get("peer_azure_app_id").(string)
+ tenantID = d.Get("peer_azure_tenant_id").(string)
+ resourceGroup = d.Get("peer_resource_group").(string)
+
+ req = organizationvpc.OrganizationVpcPeeringConnectionCreateIn{
+ PeerAzureAppId: util.ToPtr(appID),
+ PeerAzureTenantId: util.ToPtr(tenantID),
+ PeerCloudAccount: azureSubscriptionID,
+ PeerResourceGroup: util.ToPtr(resourceGroup),
+ PeerVpc: vnetName,
+ }
+ )
+
+ pCon, err := createPeeringConnection(ctx, orgID, vpcID, client, d, req)
+ if err != nil {
+ return diag.Errorf("Error creating VPC peering connection: %s", err)
+ }
+
+ diags := getDiagnosticsFromState(newOrganizationVPCPeeringState(pCon))
+
+ d.SetId(schemautil.BuildResourceID(orgID, vpcID, pCon.PeerCloudAccount, pCon.PeerVpc, pCon.PeerResourceGroup))
+
+ // in case of an error delete VPC peering connection
+ if diags.HasError() {
+ deleteDiags := resourceAzureOrgVPCPeeringConnectionDelete(ctx, d, client)
+ d.SetId("") // Clear the ID after delete
+
+ return append(diags, deleteDiags...)
+ }
+
+ return append(diags, resourceAzureOrgVPCPeeringConnectionRead(ctx, d, client)...)
+}
+
+func resourceAzureOrgVPCPeeringConnectionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ orgID, vpcID, cloudAccount, vnetName, resourceGroup, err := schemautil.SplitResourceID5(d.Id())
+ if err != nil {
+ return diag.Errorf("error parsing Azure peering VPC ID: %s", err)
+ }
+
+ vpc, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return diag.FromErr(schemautil.ResourceReadHandleNotFound(err, d))
+ }
+
+ return diag.Errorf("failed to get VPC with ID %q: %s", vpcID, err)
+ }
+
+ pc := lookupAzurePeeringConnection(vpc, cloudAccount, vnetName, resourceGroup)
+ if pc == nil {
+ d.SetId("") // Clear the ID as the resource is not found
+
+ return diag.FromErr(fmt.Errorf("VPC peering connection not found"))
+ }
+
+ if err = d.Set("organization_id", orgID); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("organization_vpc_id", vpcID); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("azure_subscription_id", pc.PeerCloudAccount); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("vnet_name", pc.PeerVpc); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("peer_azure_app_id", pc.PeerAzureAppId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("peer_azure_tenant_id", pc.PeerAzureTenantId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("peer_resource_group", pc.PeerResourceGroup); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("peering_connection_id", *pc.PeeringConnectionId); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("state", string(pc.State)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
+
+func resourceAzureOrgVPCPeeringConnectionDelete(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ orgID, vpcID, cloudAccount, vnetName, resourceGroup, err := schemautil.SplitResourceID5(d.Id())
+ if err != nil {
+ return diag.Errorf("error parsing Azure peering VPC ID: %s", err)
+ }
+
+ vpc, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return nil // consider already deleted
+ }
+
+ return diag.Errorf("failed to get VPC with ID %q: %s", vpcID, err)
+ }
+
+ if err = deletePeeringConnection(
+ ctx,
+ orgID,
+ vpcID,
+ client,
+ d,
+ lookupAzurePeeringConnection(vpc, cloudAccount, vnetName, resourceGroup),
+ ); err != nil {
+ return diag.Errorf("Error deleting Azure Aiven VPC Peering Connection: %s", err)
+ }
+
+ return nil
+}
+
+func lookupAzurePeeringConnection(
+ vpc *organizationvpc.OrganizationVpcGetOut,
+ peerCloudAccount, peerVPC, resourceGroup string,
+) *organizationvpc.OrganizationVpcGetPeeringConnectionOut {
+ for _, pc := range vpc.PeeringConnections {
+ if pc.PeerCloudAccount == peerCloudAccount &&
+ pc.PeerVpc == peerVPC &&
+ pc.PeerResourceGroup == resourceGroup {
+
+ return &pc
+ }
+ }
+
+ return nil
+}
diff --git a/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection_data_source.go b/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection_data_source.go
new file mode 100644
index 000000000..778fe1a38
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection_data_source.go
@@ -0,0 +1,36 @@
+package vpc
+
+import (
+ "context"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+func DatasourceAzureOrgVPCPeeringConnection() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: common.WithGenClientDiag(datasourceAzureOrgVPCPeeringConnectionRead),
+ Description: "Gets information about about an Azure VPC peering connection.",
+ Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenAzureOrgVPCPeeringConnectionSchema,
+ "organization_id", "organization_vpc_id", "azure_subscription_id",
+ "peer_resource_group", "vnet_name"),
+ }
+}
+
+func datasourceAzureOrgVPCPeeringConnectionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ var (
+ orgID = d.Get("organization_id").(string)
+ vpcID = d.Get("organization_vpc_id").(string)
+ subID = d.Get("azure_subscription_id").(string)
+ vnet = d.Get("vnet_name").(string)
+ rg = d.Get("peer_resource_group").(string)
+ )
+
+ d.SetId(schemautil.BuildResourceID(orgID, vpcID, subID, vnet, rg))
+
+ return resourceAzureVPCPeeringConnectionRead(ctx, d, client)
+}
diff --git a/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection_test.go b/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection_test.go
new file mode 100644
index 000000000..2e5fc341e
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/azure_org_vpc_peering_connection_test.go
@@ -0,0 +1,139 @@
+package vpc_test
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+
+ acc "github.com/aiven/terraform-provider-aiven/internal/acctest"
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+const (
+ azureOrgVPCPeeringResource = "aiven_azure_org_vpc_peering_connection"
+)
+
+func TestAccAivenAzureOrgVPCPeeringConnection(t *testing.T) {
+ var (
+ orgName = acc.SkipIfEnvVarsNotSet(t, "AIVEN_ORGANIZATION_NAME")["AIVEN_ORGANIZATION_NAME"]
+ registry = preSetAzureOrgVPCPeeringTemplates(t)
+ newComposition = func() *acc.CompositionBuilder {
+ return registry.NewCompositionBuilder().
+ Add("organization_data", map[string]any{
+ "organization_name": orgName})
+ }
+
+ subscriptionID = "00000000-0000-0000-0000-000000000000"
+ vnetName = "test-vnet"
+ resourceGroup = "test-rg"
+ appID = "00000000-0000-0000-0000-000000000000"
+ tenantID = "00000000-0000-0000-0000-000000000000"
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckAzureOrgVPCPeeringResourceDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: newComposition().
+ Add(organizationVPCResource, map[string]any{
+ "resource_name": "test_org_vpc",
+ "cloud_name": "azure-germany-westcentral",
+ "network_cidr": "10.0.0.0/24",
+ }).
+ Add(azureOrgVPCPeeringResource, map[string]any{
+ "resource_name": "test_org_vpc_peering",
+ "organization_id": acc.Reference("data.aiven_organization.foo.id"),
+ "organization_vpc_id": acc.Reference("aiven_organization_vpc.test_org_vpc.organization_vpc_id"),
+ "azure_subscription_id": acc.Literal(subscriptionID),
+ "vnet_name": acc.Literal(vnetName),
+ "peer_resource_group": acc.Literal(resourceGroup),
+ "peer_azure_app_id": acc.Literal(appID),
+ "peer_azure_tenant_id": acc.Literal(tenantID),
+ }).MustRender(t),
+ ExpectError: regexp.MustCompile(`peer_azure_app_id '.*' does not refer to a valid application object`), // Azure app ID is invalid
+ },
+ },
+ })
+}
+
+func testAccCheckAzureOrgVPCPeeringResourceDestroy(s *terraform.State) error {
+ ctx := context.Background()
+
+ c, err := acc.GetTestGenAivenClient()
+ if err != nil {
+ return fmt.Errorf("error initializing Aiven client: %w", err)
+ }
+
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != awsOrgVPCPeeringResource {
+ continue
+ }
+
+ orgID, vpcID, cloudAccount, vnetName, resourceGroup, err := schemautil.SplitResourceID5(rs.Primary.ID)
+ if err != nil {
+ return fmt.Errorf("error splitting resource with ID: %q - %w", rs.Primary.ID, err)
+ }
+
+ orgVPC, err := c.OrganizationVpcGet(ctx, orgID, vpcID)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error fetching VPC (%q): %w", vpcID, err)
+ }
+
+ if orgVPC == nil {
+ return nil // Peering connection was deleted with the VPC
+ }
+
+ var pc *organizationvpc.OrganizationVpcGetPeeringConnectionOut
+ for _, pCon := range orgVPC.PeeringConnections {
+ if pCon.PeerCloudAccount == cloudAccount && pCon.PeerVpc == vnetName && pCon.PeerResourceGroup == resourceGroup {
+ pc = &pCon
+ break
+ }
+ }
+
+ if pc != nil {
+ return fmt.Errorf("peering connection %q still exists", *pc.PeeringConnectionId)
+ }
+ }
+
+ return nil
+}
+
+func preSetAzureOrgVPCPeeringTemplates(t *testing.T) *acc.TemplateRegistry {
+ t.Helper()
+
+ registry := acc.NewTemplateRegistry(azureOrgVPCPeeringResource)
+
+ registry.MustAddTemplate(t, "organization_data", `
+data "aiven_organization" "foo" {
+ name = "{{ .organization_name }}"
+}`)
+
+ registry.MustAddTemplate(t, organizationVPCResource, `
+resource "aiven_organization_vpc" "{{ .resource_name }}" {
+ organization_id = data.aiven_organization.foo.id
+ cloud_name = "{{ .cloud_name }}"
+ network_cidr = "{{ .network_cidr }}"
+}`)
+
+ registry.MustAddTemplate(t, azureOrgVPCPeeringResource, `
+resource "aiven_azure_org_vpc_peering_connection" "{{ .resource_name }}" {
+ organization_id = {{ if .organization_id.IsLiteral }}"{{ .organization_id.Value }}"{{ else }}{{ .organization_id.Value }}{{ end }}
+ organization_vpc_id = {{ if .organization_vpc_id.IsLiteral }}"{{ .organization_vpc_id.Value }}"{{ else }}{{ .organization_vpc_id.Value }}{{ end }}
+ azure_subscription_id = {{ if .azure_subscription_id.IsLiteral }}"{{ .azure_subscription_id.Value }}"{{ else }}{{ .azure_subscription_id.Value }}{{ end }}
+ vnet_name = {{ if .vnet_name.IsLiteral }}"{{ .vnet_name.Value }}"{{ else }}{{ .vnet_name.Value }}{{ end }}
+ peer_resource_group = {{ if .peer_resource_group.IsLiteral }}"{{ .peer_resource_group.Value }}"{{ else }}{{ .peer_resource_group.Value }}{{ end }}
+ peer_azure_app_id = {{ if .peer_azure_app_id.IsLiteral }}"{{ .peer_azure_app_id.Value }}"{{ else }}{{ .peer_azure_app_id.Value }}{{ end }}
+ peer_azure_tenant_id = {{ if .peer_azure_tenant_id.IsLiteral }}"{{ .peer_azure_tenant_id.Value }}"{{ else }}{{ .peer_azure_tenant_id.Value }}{{ end }}
+}`)
+
+ return registry
+}
diff --git a/internal/sdkprovider/service/vpc/azure_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/azure_vpc_peering_connection.go
index 6135b4422..24d99b11a 100644
--- a/internal/sdkprovider/service/vpc/azure_vpc_peering_connection.go
+++ b/internal/sdkprovider/service/vpc/azure_vpc_peering_connection.go
@@ -174,7 +174,7 @@ func resourceAzureVPCPeeringConnectionCreate(ctx context.Context, d *schema.Reso
}
pc = res.(*aiven.VPCPeeringConnection)
- diags := getDiagnosticsFromState(pc)
+ diags := getDiagnosticsFromState(newAivenVPCPeeringState(pc))
d.SetId(schemautil.BuildResourceID(projectName, vpcID, pc.PeerCloudAccount, pc.PeerVPC))
diff --git a/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection.go
new file mode 100644
index 000000000..6ec7a574e
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection.go
@@ -0,0 +1,197 @@
+package vpc
+
+import (
+ "context"
+ "fmt"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig"
+)
+
+var aivenGCPOrgVPCPeeringConnectionSchema = map[string]*schema.Schema{
+ "organization_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Identifier of the organization.",
+ },
+ "organization_vpc_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Identifier of the organization VPC.",
+ },
+ "gcp_project_id": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("Google Cloud project ID.").ForceNew().Build(),
+ },
+ "peer_vpc": {
+ ForceNew: true,
+ Required: true,
+ Type: schema.TypeString,
+ Description: userconfig.Desc("Google Cloud VPC network name.").ForceNew().Build(),
+ },
+ "state": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: "State of the peering connection.",
+ },
+ "self_link": {
+ Computed: true,
+ Type: schema.TypeString,
+ Description: "Computed Google Cloud network peering link.",
+ },
+}
+
+func ResourceGCPOrgVPCPeeringConnection() *schema.Resource {
+ return &schema.Resource{
+ Description: "Creates and manages a Google Cloud VPC peering connection.",
+ CreateContext: common.WithGenClientDiag(resourceGCPOrgVPCPeeringConnectionCreate),
+ ReadContext: common.WithGenClientDiag(resourceGCPOrgVPCPeeringConnectionRead),
+ DeleteContext: common.WithGenClientDiag(resourceGCPOrgVPCPeeringConnectionDelete),
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Timeouts: schemautil.DefaultResourceTimeouts(),
+
+ Schema: aivenGCPOrgVPCPeeringConnectionSchema,
+ }
+}
+
+func resourceGCPOrgVPCPeeringConnectionCreate(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ var (
+ orgID = d.Get("organization_id").(string)
+ vpcID = d.Get("organization_vpc_id").(string)
+ gcpProjectID = d.Get("gcp_project_id").(string)
+ peerVPC = d.Get("peer_vpc").(string)
+
+ req = organizationvpc.OrganizationVpcPeeringConnectionCreateIn{
+ PeerCloudAccount: gcpProjectID,
+ PeerVpc: peerVPC,
+ }
+ )
+
+ pCon, err := createPeeringConnection(ctx, orgID, vpcID, client, d, req)
+ if err != nil {
+ return diag.Errorf("Error creating VPC peering connection: %s", err)
+ }
+
+ diags := getDiagnosticsFromState(newOrganizationVPCPeeringState(pCon))
+
+ d.SetId(schemautil.BuildResourceID(orgID, vpcID, pCon.PeerCloudAccount, pCon.PeerVpc))
+
+ // in case of an error delete VPC peering connection
+ if diags.HasError() {
+ deleteDiags := resourceAzureOrgVPCPeeringConnectionDelete(ctx, d, client)
+ d.SetId("") // Clear the ID after delete
+
+ return append(diags, deleteDiags...)
+ }
+
+ return append(diags, resourceGCPOrgVPCPeeringConnectionRead(ctx, d, client)...)
+}
+
+func resourceGCPOrgVPCPeeringConnectionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ orgID, vpcID, cloudAcc, peerVPC, err := schemautil.SplitResourceID4(d.Id())
+ if err != nil {
+ return diag.Errorf("error parsing GCP peering VPC ID: %s", err)
+ }
+
+ vpc, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return diag.FromErr(schemautil.ResourceReadHandleNotFound(err, d))
+ }
+
+ return diag.Errorf("failed to get VPC with ID %q: %s", vpcID, err)
+ }
+
+ pCon := lookupGCPPeeringConnection(vpc, cloudAcc, peerVPC)
+ if pCon == nil {
+ d.SetId("") // Set ID to clear the state
+
+ return diag.FromErr(fmt.Errorf("VPC peering connection not found"))
+ }
+
+ if err = d.Set("organization_id", orgID); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("organization_vpc_id", vpcID); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("gcp_project_id", pCon.PeerCloudAccount); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("peer_vpc", pCon.PeerVpc); err != nil {
+ return diag.FromErr(err)
+ }
+ if err = d.Set("state", string(pCon.State)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ // Set self_link, so it can be used for google_compute_network_peering if needed
+ if pCon.StateInfo.ToProjectId != nil && pCon.StateInfo.ToVpcNetwork != nil {
+ selfLink := fmt.Sprintf("%s/projects/%s/global/networks/%s",
+ _gcpAPI,
+ *pCon.StateInfo.ToProjectId,
+ *pCon.StateInfo.ToVpcNetwork)
+
+ if err = d.Set("self_link", selfLink); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+
+ return nil
+}
+
+func resourceGCPOrgVPCPeeringConnectionDelete(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ orgID, vpcID, cloudAcc, peerVPC, err := schemautil.SplitResourceID4(d.Id())
+ if err != nil {
+ return diag.Errorf("error parsing GCP peering VPC ID: %s", err)
+ }
+
+ vpc, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return nil // consider already deleted
+ }
+
+ return diag.Errorf("failed to get VPC with ID %q: %s", vpcID, err)
+ }
+
+ if err = deletePeeringConnection(
+ ctx,
+ orgID,
+ vpcID,
+ client,
+ d,
+ lookupGCPPeeringConnection(vpc, cloudAcc, peerVPC),
+ ); err != nil {
+ return diag.Errorf("Error deleting GCP Aiven VPC Peering Connection: %s", err)
+ }
+
+ return nil
+}
+
+func lookupGCPPeeringConnection(
+ vpc *organizationvpc.OrganizationVpcGetOut,
+ cloudAcc, peerVPC string,
+) *organizationvpc.OrganizationVpcGetPeeringConnectionOut {
+ var pCon *organizationvpc.OrganizationVpcGetPeeringConnectionOut
+ for _, p := range vpc.PeeringConnections {
+ if p.PeerCloudAccount == cloudAcc && p.PeerVpc == peerVPC && p.PeeringConnectionId != nil {
+ pCon = &p
+ break
+ }
+ }
+
+ return pCon
+}
diff --git a/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection_data_source.go b/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection_data_source.go
new file mode 100644
index 000000000..2a56a0b0a
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection_data_source.go
@@ -0,0 +1,34 @@
+package vpc
+
+import (
+ "context"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+func DatasourceGCPOrgVPCPeeringConnection() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: common.WithGenClientDiag(datasourceGCPOrgVPCPeeringConnectionRead),
+ Description: "The GCP VPC Peering Connection data source provides information about the existing Aiven VPC Peering Connection.",
+ Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenGCPOrgVPCPeeringConnectionSchema,
+ "organization_id", "organization_vpc_id", "gcp_project_id", "peer_vpc"),
+ }
+}
+
+func datasourceGCPOrgVPCPeeringConnectionRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) diag.Diagnostics {
+ var (
+ orgID = d.Get("organization_id").(string)
+ vpcID = d.Get("organization_vpc_id").(string)
+ gcpProjectID = d.Get("gcp_project_id").(string)
+ peerVPC = d.Get("peer_vpc").(string)
+ )
+
+ d.SetId(schemautil.BuildResourceID(orgID, vpcID, gcpProjectID, peerVPC))
+
+ return resourceGCPOrgVPCPeeringConnectionRead(ctx, d, client)
+}
diff --git a/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection_test.go b/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection_test.go
new file mode 100644
index 000000000..760e8265f
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/gcp_org_vpc_peering_connection_test.go
@@ -0,0 +1,233 @@
+package vpc_test
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+
+ acc "github.com/aiven/terraform-provider-aiven/internal/acctest"
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+const (
+ gcpOrgVPCPeeringResource = "aiven_gcp_org_vpc_peering_connection"
+)
+
+// TestAccAivenGCPOrgVPCPeeringConnection tests the GCP VPC peering connection resource functionality.
+// Since creating a real GCP VPC peering connection in CI requires valid GCP credentials, this test:
+// 1. Sets up a test environment with an invalid GCP project ID and VPC ID
+// 2. Attempts to create an Aiven VPC and a peering connection
+// 3. Validates that the creation fails with the expected error due to invalid GCP project ID
+func TestAccAivenGCPOrgVPCPeeringConnection(t *testing.T) {
+ var (
+ orgName = acc.SkipIfEnvVarsNotSet(t, "AIVEN_ORGANIZATION_NAME")["AIVEN_ORGANIZATION_NAME"]
+ registry = preSetGcpOrgVPCPeeringTemplates(t)
+ newComposition = func() *acc.CompositionBuilder {
+ return registry.NewCompositionBuilder().
+ Add("organization_data", map[string]any{
+ "organization_name": orgName})
+ }
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckGCPOrgVPCPeeringResourceDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: newComposition().
+ Add(organizationVPCResource, map[string]any{
+ "resource_name": "test_org_vpc",
+ "cloud_name": "google-europe-west10",
+ "network_cidr": "10.0.0.0/24",
+ }).
+ Add(gcpOrgVPCPeeringResource, map[string]any{
+ "resource_name": "test_org_vpc_peering",
+ "organization_id": acc.Reference("data.aiven_organization.foo.id"),
+ "organization_vpc_id": acc.Reference("aiven_organization_vpc.test_org_vpc.organization_vpc_id"),
+ "gcp_project_id": acc.Literal("wrong_project_id"),
+ "peer_vpc": acc.Literal("wrong_peer_vpc"),
+ }).MustRender(t),
+ ExpectError: regexp.MustCompile(`peer_cloud_account must be a valid GCP project ID`), // Expected error due to invalid GCP arguments
+ },
+ },
+ })
+}
+
+// TestAccAivenGCPOrgVPCPeeringConnectionFull tests the complete GCP VPC peering connection workflow
+// with real GCP resources. This test:
+// 1. Creates a GCP VPC and route
+// 2. Creates an Aiven Organization VPC
+// 3. Establishes VPC peering between GCP and Aiven VPCs
+// 4. Sets up routing for the peered VPCs
+//
+// Note: The test will be skipped in CI environments since it requires real GCP credentials
+// and resources. This test is meant for local development and verification for now.
+// Prerequisites:
+// - Valid GCP credentials with permissions to create/delete VPC resources
+// - GCP project with VPC API enabled
+// - Required permissions: VPC creation/deletion, VPC peering, route management
+func TestAccAivenGCPOrgVPCPeeringConnectionFull(t *testing.T) {
+ var envVars = acc.SkipIfEnvVarsNotSet(
+ t,
+ "AIVEN_ORGANIZATION_NAME",
+ "GOOGLE_PROJECT",
+ )
+
+ var (
+ orgName = envVars["AIVEN_ORGANIZATION_NAME"]
+ gcpProject = envVars["GOOGLE_PROJECT"]
+ gcpRegion = "europe-west10"
+ registry = preSetGcpOrgVPCPeeringTemplates(t)
+ newComposition = func() *acc.CompositionBuilder {
+ return registry.NewCompositionBuilder().
+ Add("gcp_provider", map[string]any{
+ "gcp_project": gcpProject,
+ "gcp_region": gcpRegion}).
+ Add("organization_data", map[string]any{
+ "organization_name": orgName})
+ }
+
+ resourceName = fmt.Sprintf("%s.%s", gcpOrgVPCPeeringResource, "test_peering")
+
+ randName = acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
+ serviceName = fmt.Sprintf("test-acc-%s", randName)
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ CheckDestroy: testAccCheckGCPOrgVPCPeeringResourceDestroy,
+ ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories,
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "google": {
+ Source: "hashicorp/google",
+ VersionConstraint: "=6.15.0",
+ },
+ },
+ Steps: []resource.TestStep{
+ {
+ Config: newComposition().
+ Add("google_vpc", map[string]any{
+ "resource_name": "example",
+ "vpc_name": fmt.Sprintf("%s-vpc", serviceName),
+ }).
+ Add(organizationVPCResource, map[string]any{
+ "resource_name": "example",
+ "cloud_name": fmt.Sprintf("google-%s", gcpRegion),
+ "network_cidr": "10.0.0.0/24",
+ }).
+ Add(gcpOrgVPCPeeringResource, map[string]any{
+ "resource_name": "test_peering",
+ "organization_id": acc.Reference("data.aiven_organization.foo.id"),
+ "organization_vpc_id": acc.Reference("aiven_organization_vpc.example.organization_vpc_id"),
+ "gcp_project_id": acc.Literal(gcpProject),
+ "peer_vpc": acc.Reference("google_compute_network.example.name"),
+ }).MustRender(t),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrPair(resourceName, "organization_id", "data.aiven_organization.foo", "id"),
+ resource.TestCheckResourceAttrPair(resourceName, "organization_vpc_id", "aiven_organization_vpc.example", "organization_vpc_id"),
+ resource.TestCheckResourceAttr(resourceName, "gcp_project_id", gcpProject),
+ resource.TestCheckResourceAttrPair(resourceName, "peer_vpc", "google_compute_network.example", "name"),
+ resource.TestCheckResourceAttrSet(resourceName, "state"),
+ resource.TestCheckResourceAttrSet(resourceName, "self_link"),
+ ),
+ },
+ {
+ // importing the resource
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func testAccCheckGCPOrgVPCPeeringResourceDestroy(s *terraform.State) error {
+ ctx := context.Background()
+
+ c, err := acc.GetTestGenAivenClient()
+ if err != nil {
+ return fmt.Errorf("error initializing Aiven client: %w", err)
+ }
+
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != gcpOrgVPCPeeringResource {
+ continue
+ }
+
+ orgID, vpcID, cloudAcc, peerVPC, err := schemautil.SplitResourceID4(rs.Primary.ID)
+ if err != nil {
+ return fmt.Errorf("error parsing GCP peering VPC ID: %q. %w", rs.Primary.ID, err)
+ }
+
+ orgVPC, err := c.OrganizationVpcGet(ctx, orgID, vpcID)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error fetching VPC (%q): %w", orgID, err)
+ }
+
+ if orgVPC == nil {
+ return nil // Peering connection was deleted with the VPC
+ }
+
+ var pc *organizationvpc.OrganizationVpcGetPeeringConnectionOut
+ for _, p := range orgVPC.PeeringConnections {
+ if p.PeerCloudAccount == cloudAcc && p.PeerVpc == peerVPC && p.PeeringConnectionId != nil {
+ pc = &p
+ break
+ }
+ }
+
+ if pc != nil {
+ return fmt.Errorf("peering connection %q still exists", *pc.PeeringConnectionId)
+ }
+ }
+
+ return nil
+}
+
+func preSetGcpOrgVPCPeeringTemplates(t *testing.T) *acc.TemplateRegistry {
+ t.Helper()
+
+ registry := acc.NewTemplateRegistry(gcpOrgVPCPeeringResource)
+
+ registry.MustAddTemplate(t, "organization_data", `
+data "aiven_organization" "foo" {
+ name = "{{ .organization_name }}"
+}`)
+
+ registry.MustAddTemplate(t, "gcp_provider", `
+provider "google" {
+ project = "{{ .gcp_project }}"
+ region = "{{ .gcp_region }}"
+}`)
+
+ registry.MustAddTemplate(t, organizationVPCResource, `
+resource "aiven_organization_vpc" "{{ .resource_name }}" {
+ organization_id = data.aiven_organization.foo.id
+ cloud_name = "{{ .cloud_name }}"
+ network_cidr = "{{ .network_cidr }}"
+}`)
+
+ registry.MustAddTemplate(t, gcpOrgVPCPeeringResource, `
+resource "aiven_gcp_org_vpc_peering_connection" "{{ .resource_name }}" {
+ organization_id = {{ if .organization_id.IsLiteral }}"{{ .organization_id.Value }}"{{ else }}{{ .organization_id.Value }}{{ end }}
+ organization_vpc_id = {{ if .organization_vpc_id.IsLiteral }}"{{ .organization_vpc_id.Value }}"{{ else }}{{ .organization_vpc_id.Value }}{{ end }}
+ gcp_project_id = {{ if .gcp_project_id.IsLiteral }}"{{ .gcp_project_id.Value }}"{{ else }}{{ .gcp_project_id.Value }}{{ end }}
+ peer_vpc = {{ if .peer_vpc.IsLiteral }}"{{ .peer_vpc.Value }}"{{ else }}{{ .peer_vpc.Value }}{{ end }}
+}`)
+
+ registry.MustAddTemplate(t, "google_vpc", `
+resource "google_compute_network" "{{ .resource_name }}" {
+ name = "{{ .vpc_name }}"
+ auto_create_subnetworks = false
+}`)
+
+ return registry
+}
diff --git a/internal/sdkprovider/service/vpc/gcp_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/gcp_vpc_peering_connection.go
index c9bf6d80f..416065917 100644
--- a/internal/sdkprovider/service/vpc/gcp_vpc_peering_connection.go
+++ b/internal/sdkprovider/service/vpc/gcp_vpc_peering_connection.go
@@ -147,7 +147,7 @@ func resourceGCPVPCPeeringConnectionCreate(ctx context.Context, d *schema.Resour
}
pc = res.(*aiven.VPCPeeringConnection)
- diags := getDiagnosticsFromState(pc)
+ diags := getDiagnosticsFromState(newAivenVPCPeeringState(pc))
d.SetId(schemautil.BuildResourceID(projectName, vpcID, pc.PeerCloudAccount, pc.PeerVPC))
diff --git a/internal/sdkprovider/service/vpc/org_vpc_peering_connection.go b/internal/sdkprovider/service/vpc/org_vpc_peering_connection.go
new file mode 100644
index 000000000..26f3ba79b
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/org_vpc_peering_connection.go
@@ -0,0 +1,149 @@
+package vpc
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+)
+
+var (
+ pollInterval = common.DefaultStateChangeMinTimeout
+ pollDelay = 1 * time.Second
+)
+
+func createPeeringConnection(
+ ctx context.Context,
+ orgID, orgVpcID string,
+ client avngen.Client,
+ d *schema.ResourceData,
+ req organizationvpc.OrganizationVpcPeeringConnectionCreateIn,
+) (*organizationvpc.OrganizationVpcGetPeeringConnectionOut, error) {
+ pc, err := client.OrganizationVpcPeeringConnectionCreate(
+ ctx,
+ orgID,
+ orgVpcID,
+ &req,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("error creating VPC peering connection: %w", err)
+ }
+
+ if pc.PeeringConnectionId == nil {
+ return nil, fmt.Errorf("error creating VPC peering connection: missing peering connection ID")
+ }
+
+ // wait for the VPC peering connection to be approved
+ stateChangeConf := &retry.StateChangeConf{
+ Target: []string{""}, // empty target means we don't care about the target state
+ Pending: []string{string(organizationvpc.VpcPeeringConnectionStateTypeApproved)},
+ Refresh: func() (any, string, error) {
+ resp, err := client.OrganizationVpcGet(ctx, orgID, orgVpcID)
+ if err != nil {
+ return nil, "", fmt.Errorf("error getting VPC: %w", err)
+ }
+
+ pCon := lookupPeeringConnection(resp, *pc.PeeringConnectionId)
+ if pCon == nil {
+ return nil, "", fmt.Errorf("VPC peering connection not found")
+ }
+
+ if pCon.State == organizationvpc.VpcPeeringConnectionStateTypeApproved {
+ return pCon, string(pCon.State), nil
+ }
+
+ return pCon, "", nil // return empty target state to stop the loop
+ },
+ Delay: pollDelay,
+ MinTimeout: pollInterval,
+ Timeout: d.Timeout(schema.TimeoutCreate),
+ }
+
+ resp, err := stateChangeConf.WaitForStateContext(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("error waiting for VPC peering connection to change state: %w", err)
+ }
+
+ pCon, ok := resp.(*organizationvpc.OrganizationVpcGetPeeringConnectionOut)
+ if !ok {
+ return nil, fmt.Errorf("error creating VPC peering connection: invalid response") // this should never happen
+ }
+
+ return pCon, nil
+}
+
+func deletePeeringConnection(
+ ctx context.Context,
+ orgID, orgVpcID string,
+ client avngen.Client,
+ d *schema.ResourceData,
+ pc *organizationvpc.OrganizationVpcGetPeeringConnectionOut,
+) error {
+ if pc == nil {
+ return nil // consider already deleted
+ }
+
+ _, err := client.OrganizationVpcPeeringConnectionDeleteById(
+ ctx,
+ orgID,
+ orgVpcID,
+ *pc.PeeringConnectionId,
+ )
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return nil // consider already deleted
+ }
+
+ return fmt.Errorf("error deleting VPC peering connection: %w", err)
+ }
+
+ stateChangeConf := &retry.StateChangeConf{
+ Target: []string{string(organizationvpc.VpcPeeringConnectionStateTypeDeleted)},
+ Refresh: func() (interface{}, string, error) {
+ resp, err := client.OrganizationVpcGet(ctx, orgID, orgVpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ return struct{}{}, string(organizationvpc.VpcPeeringConnectionStateTypeDeleted), nil
+ }
+
+ return nil, "", fmt.Errorf("error getting VPC: %w", err)
+ }
+
+ pCon := lookupPeeringConnection(resp, *pc.PeeringConnectionId)
+ if pCon == nil {
+ // return empty struct to signal to the state change function that the resource is deleted
+ return struct{}{}, string(organizationvpc.VpcPeeringConnectionStateTypeDeleted), nil
+ }
+
+ return pCon, string(pCon.State), nil
+ },
+ Delay: pollDelay,
+ Timeout: d.Timeout(schema.TimeoutDelete),
+ MinTimeout: pollInterval,
+ }
+
+ if _, err = stateChangeConf.WaitForStateContext(ctx); err != nil && !avngen.IsNotFound(err) {
+ return fmt.Errorf("error waiting for deletion: %w", err)
+ }
+
+ return nil
+}
+
+func lookupPeeringConnection(
+ vpc *organizationvpc.OrganizationVpcGetOut,
+ peeringConnectionID string,
+) *organizationvpc.OrganizationVpcGetPeeringConnectionOut {
+ for _, pCon := range vpc.PeeringConnections {
+ if pCon.PeeringConnectionId != nil && *pCon.PeeringConnectionId == peeringConnectionID {
+ return &pCon
+ }
+ }
+
+ return nil
+}
diff --git a/internal/sdkprovider/service/vpc/org_vpc_peering_connection_test.go b/internal/sdkprovider/service/vpc/org_vpc_peering_connection_test.go
new file mode 100644
index 000000000..f4da22f57
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/org_vpc_peering_connection_test.go
@@ -0,0 +1,454 @@
+package vpc
+
+import (
+ "context"
+ "errors"
+ "reflect"
+ "sync"
+ "testing"
+ "time"
+ "unsafe"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/samber/lo"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+
+ "github.com/aiven/terraform-provider-aiven/mocks"
+)
+
+var testMu = &sync.RWMutex{}
+
+func TestCreatePeeringConnection(t *testing.T) {
+ t.Parallel()
+
+ var (
+ ctx = context.Background()
+ mockClient = mocks.NewMockClient(t)
+ d = schema.TestResourceDataRaw(t, nil, nil)
+
+ orgID = uuid.New().String()
+ vpcID = uuid.New().String()
+ )
+
+ testCases := []struct {
+ name string
+ setupMocks func() *mocks.MockClient
+ expectedState organizationvpc.VpcPeeringConnectionStateType
+ expectError bool
+ }{
+ {
+ name: "successful creation and approval",
+ expectedState: organizationvpc.VpcPeeringConnectionStateTypeActive,
+ setupMocks: func() *mocks.MockClient {
+ pcID := uuid.New().String()
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 1*time.Second)
+
+ // Setup create response
+ mc.On("OrganizationVpcPeeringConnectionCreate",
+ ctx,
+ orgID,
+ vpcID,
+ mock.AnythingOfType("*organizationvpc.OrganizationVpcPeeringConnectionCreateIn"),
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionCreateOut{
+ PeeringConnectionId: &pcID,
+ }, nil).Once()
+
+ // Setup first get response (pending)
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ {
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeApproved,
+ },
+ },
+ }, nil).Once()
+
+ // Setup second get response (approved)
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ {
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeActive,
+ },
+ },
+ }, nil).Once()
+
+ return mc
+ },
+ },
+ {
+ name: "creation fails",
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+
+ mc.On("OrganizationVpcPeeringConnectionCreate",
+ ctx,
+ orgID,
+ vpcID,
+ mock.AnythingOfType("*organizationvpc.OrganizationVpcPeeringConnectionCreateIn"),
+ ).Return(nil, errors.New("creation failed")).Once()
+
+ return mc
+ },
+ expectError: true,
+ },
+ {
+ name: "approval timeout",
+ setupMocks: func() *mocks.MockClient {
+ pcID := uuid.New().String()
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 100*time.Millisecond)
+
+ mc.On("OrganizationVpcPeeringConnectionCreate",
+ ctx,
+ orgID,
+ vpcID,
+ mock.AnythingOfType("*organizationvpc.OrganizationVpcPeeringConnectionCreateIn"),
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionCreateOut{
+ PeeringConnectionId: &pcID,
+ }, nil)
+
+ // Always return pending state
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ {
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeApproved,
+ },
+ },
+ }, nil)
+
+ return mc
+ },
+ expectError: true,
+ },
+ {
+ name: "peering connection disappears",
+ setupMocks: func() *mocks.MockClient {
+ pcID := uuid.New().String()
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 1*time.Second)
+
+ mc.On("OrganizationVpcPeeringConnectionCreate",
+ ctx,
+ orgID,
+ vpcID,
+ mock.AnythingOfType("*organizationvpc.OrganizationVpcPeeringConnectionCreateIn"),
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionCreateOut{
+ PeeringConnectionId: &pcID,
+ }, nil).Once()
+
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{},
+ }, nil).Once()
+
+ return mc
+ },
+ expectError: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ mc := tc.setupMocks()
+
+ result, err := createPeeringConnection(
+ ctx,
+ orgID,
+ vpcID,
+ mc,
+ d,
+ organizationvpc.OrganizationVpcPeeringConnectionCreateIn{},
+ )
+
+ if tc.expectError {
+ assert.Error(t, err)
+ assert.Nil(t, result)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, result)
+ assert.Equal(t, tc.expectedState, result.State)
+ }
+
+ mockClient.AssertExpectations(t)
+ })
+ }
+}
+
+func setTimeouts(t *testing.T, rd *schema.ResourceData, delay, interval, timeout time.Duration) {
+ t.Helper()
+
+ testMu.Lock()
+ originalDelay := pollDelay
+ originalInterval := pollInterval
+ pollDelay = delay
+ pollInterval = interval
+ testMu.Unlock()
+
+ t.Cleanup(func() {
+ testMu.Lock()
+ pollDelay = originalDelay
+ pollInterval = originalInterval
+ testMu.Unlock()
+ })
+
+ timeouts := &schema.ResourceTimeout{
+ Create: lo.ToPtr(timeout),
+ Read: lo.ToPtr(timeout),
+ Update: lo.ToPtr(timeout),
+ Delete: lo.ToPtr(timeout),
+ }
+
+ val := reflect.ValueOf(rd).Elem()
+ field := val.FieldByName("timeouts")
+ reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflect.ValueOf(timeouts))
+}
+
+func TestDeletePeeringConnection(t *testing.T) {
+ t.Parallel()
+
+ var (
+ ctx = context.Background()
+ d = schema.TestResourceDataRaw(t, nil, nil)
+ orgID = uuid.New().String()
+ vpcID = uuid.New().String()
+ pcID = uuid.New().String()
+ pc = &organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeActive,
+ }
+ )
+
+ testCases := []struct {
+ name string
+ setupMocks func() *mocks.MockClient
+ inputPC *organizationvpc.OrganizationVpcGetPeeringConnectionOut
+ expectError bool
+ }{
+ {
+ name: "nil peering connection",
+ inputPC: nil,
+ setupMocks: func() *mocks.MockClient {
+ return mocks.NewMockClient(t)
+ },
+ expectError: false,
+ },
+ {
+ name: "successful deletion",
+ inputPC: pc,
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 2*time.Second)
+
+ // Setup delete response
+ mc.On("OrganizationVpcPeeringConnectionDeleteById",
+ ctx,
+ orgID,
+ vpcID,
+ pcID,
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionDeleteByIdOut{}, nil).Once()
+
+ // First get after delete shows connection still exists
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ {
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeDeleting,
+ },
+ },
+ }, nil).Once()
+
+ // Second get shows connection is gone
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ {
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeDeleted,
+ },
+ },
+ }, nil).Once()
+
+ return mc
+ },
+ expectError: false,
+ },
+ {
+ name: "delete returns not found",
+ inputPC: pc,
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 2*time.Second)
+
+ // Setup delete response with not found error
+ mc.On("OrganizationVpcPeeringConnectionDeleteById",
+ ctx,
+ orgID,
+ vpcID,
+ pcID,
+ ).Return(nil, avngen.Error{Status: 404}).Once()
+
+ return mc
+ },
+ expectError: false,
+ },
+ {
+ name: "delete fails with error",
+ inputPC: pc,
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 2*time.Second)
+
+ mc.On("OrganizationVpcPeeringConnectionDeleteById",
+ ctx,
+ orgID,
+ vpcID,
+ pcID,
+ ).Return(nil, errors.New("delete failed")).Once()
+
+ return mc
+ },
+ expectError: true,
+ },
+ {
+ name: "get after delete fails with non-404 error",
+ inputPC: pc,
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 2*time.Second)
+
+ // Setup delete response
+ mc.On("OrganizationVpcPeeringConnectionDeleteById",
+ ctx,
+ orgID,
+ vpcID,
+ pcID,
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionDeleteByIdOut{}, nil).Once()
+
+ // Get fails with error
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(nil, errors.New("get failed")).Once()
+
+ return mc
+ },
+ expectError: true,
+ },
+ {
+ name: "get after delete returns 404",
+ inputPC: pc,
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 2*time.Second)
+
+ // Setup delete response
+ mc.On("OrganizationVpcPeeringConnectionDeleteById",
+ ctx,
+ orgID,
+ vpcID,
+ pcID,
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionDeleteByIdOut{}, nil).Once()
+
+ // Get returns 404
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(nil, avngen.Error{Status: 404}).Once()
+
+ return mc
+ },
+ expectError: false,
+ },
+ {
+ name: "deletion timeout",
+ inputPC: pc,
+ setupMocks: func() *mocks.MockClient {
+ mc := mocks.NewMockClient(t)
+ setTimeouts(t, d, 10*time.Millisecond, 10*time.Millisecond, 1*time.Second)
+
+ // Setup delete response
+ mc.On("OrganizationVpcPeeringConnectionDeleteById",
+ ctx,
+ orgID,
+ vpcID,
+ pcID,
+ ).Return(&organizationvpc.OrganizationVpcPeeringConnectionDeleteByIdOut{}, nil).Once()
+
+ // Always return the connection in deleting state
+ mc.On("OrganizationVpcGet",
+ ctx,
+ orgID,
+ vpcID,
+ ).Return(&organizationvpc.OrganizationVpcGetOut{
+ PeeringConnections: []organizationvpc.OrganizationVpcGetPeeringConnectionOut{
+ {
+ PeeringConnectionId: &pcID,
+ State: organizationvpc.VpcPeeringConnectionStateTypeDeleting,
+ },
+ },
+ }, nil)
+
+ return mc
+ },
+ expectError: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ mc := tc.setupMocks()
+
+ err := deletePeeringConnection(
+ ctx,
+ orgID,
+ vpcID,
+ mc,
+ d,
+ tc.inputPC,
+ )
+
+ if tc.expectError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+
+ mc.AssertExpectations(t)
+ })
+ }
+}
diff --git a/internal/sdkprovider/service/vpc/organization_vpc.go b/internal/sdkprovider/service/vpc/organization_vpc.go
new file mode 100644
index 000000000..ee4ba548a
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/organization_vpc.go
@@ -0,0 +1,203 @@
+package vpc
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig"
+)
+
+var aivenOrganizationVPCSchema = map[string]*schema.Schema{
+ "organization_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The ID of the organization.",
+ },
+ "cloud_name": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: userconfig.Desc("The cloud provider and region where the service is hosted in the format `CLOUD_PROVIDER-REGION_NAME`. For example, `google-europe-west1` or `aws-us-east-2`.").ForceNew().Build(),
+ },
+ "network_cidr": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ ValidateFunc: validation.IsCIDR,
+ Description: "Network address range used by the VPC. For example, `192.168.0.0/24`.",
+ },
+ "organization_vpc_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The ID of the Aiven Organization VPC.",
+ },
+ "state": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: userconfig.Desc("State of the VPC.").PossibleValuesString(organizationvpc.VpcStateTypeChoices()...).Build(),
+ },
+ "create_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Time of creation of the VPC.",
+ },
+ "update_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Time of the last update of the VPC.",
+ },
+}
+
+func ResourceOrganizationVPC() *schema.Resource {
+ return &schema.Resource{
+ Description: "Creates and manages a VPC for an Aiven organization.",
+ CreateContext: common.WithGenClient(resourceOrganizationVPCCreate),
+ ReadContext: common.WithGenClient(resourceOrganizationVPCRead),
+ DeleteContext: common.WithGenClient(resourceOrganizationVPCDelete),
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Timeouts: schemautil.DefaultResourceTimeouts(),
+
+ Schema: aivenOrganizationVPCSchema,
+ }
+}
+
+func resourceOrganizationVPCCreate(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
+ var (
+ orgID = d.Get("organization_id").(string)
+ cloud = d.Get("cloud_name").(string)
+ cidr = d.Get("network_cidr").(string)
+ )
+
+ resp, err := client.OrganizationVpcCreate(ctx, orgID, &organizationvpc.OrganizationVpcCreateIn{
+ Clouds: []organizationvpc.CloudIn{
+ {
+ CloudName: cloud,
+ NetworkCidr: cidr,
+ },
+ },
+ PeeringConnections: make([]organizationvpc.PeeringConnectionIn, 0), // nil here would cause an error from the API
+ })
+ if err != nil {
+ return err
+ }
+
+ // Wait for VPC to become active
+ stateConf := &retry.StateChangeConf{
+ Pending: []string{string(organizationvpc.VpcStateTypeApproved)},
+ Target: []string{string(organizationvpc.VpcStateTypeActive)},
+ Refresh: func() (interface{}, string, error) {
+ orgVPC, err := client.OrganizationVpcGet(ctx, orgID, resp.OrganizationVpcId)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return orgVPC, string(orgVPC.State), nil
+ },
+ Timeout: d.Timeout(schema.TimeoutCreate),
+ Delay: 1 * time.Second,
+ MinTimeout: common.DefaultStateChangeMinTimeout,
+ }
+
+ _, err = stateConf.WaitForStateContext(ctx)
+ if err != nil {
+ return fmt.Errorf("error waiting for VPC (%q) to become active: %w", resp.OrganizationVpcId, err)
+ }
+
+ d.SetId(schemautil.BuildResourceID(orgID, resp.OrganizationVpcId))
+
+ return resourceOrganizationVPCRead(ctx, d, client)
+}
+
+func resourceOrganizationVPCRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
+ orgID, vpcID, err := schemautil.SplitResourceID2(d.Id())
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ return err
+ }
+
+ // currently we support only 1 cloud per VPC
+ if len(resp.Clouds) != 1 {
+ return fmt.Errorf("expected exactly 1 cloud, got %d", len(resp.Clouds))
+ }
+
+ if err = d.Set("organization_id", orgID); err != nil {
+ return err
+ }
+ if err = d.Set("organization_vpc_id", resp.OrganizationVpcId); err != nil {
+ return err
+ }
+ if err = d.Set("cloud_name", resp.Clouds[0].CloudName); err != nil {
+ return err
+ }
+ if err = d.Set("network_cidr", resp.Clouds[0].NetworkCidr); err != nil {
+ return err
+ }
+ if err = d.Set("state", resp.State); err != nil {
+ return err
+ }
+ if err = d.Set("create_time", resp.CreateTime.String()); err != nil {
+ return err
+ }
+ if err = d.Set("update_time", resp.UpdateTime.String()); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func resourceOrganizationVPCDelete(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
+ orgID, vpcID, err := schemautil.SplitResourceID2(d.Id())
+ if err != nil {
+ return err
+ }
+
+ _, err = client.OrganizationVpcDelete(ctx, orgID, vpcID)
+ if common.IsCritical(err) {
+ return err
+ }
+
+ // Wait for VPC to be deleted
+ stateConf := &retry.StateChangeConf{
+ Target: []string{string(organizationvpc.VpcStateTypeDeleted)},
+ Refresh: func() (interface{}, string, error) {
+ orgVPC, err := client.OrganizationVpcGet(ctx, orgID, vpcID)
+ if err != nil {
+ if avngen.IsNotFound(err) {
+ // if resource is not found, it's considered deleted
+ // return empty struct instead of nil to avoid the "not found" counter behavior in StateChangeConf when Target states are specified
+ return struct{}{}, string(organizationvpc.VpcStateTypeDeleted), nil
+ }
+
+ return nil, "", err
+ }
+
+ return orgVPC, string(orgVPC.State), nil
+ },
+ Timeout: d.Timeout(schema.TimeoutDelete),
+ Delay: 1 * time.Second,
+ MinTimeout: common.DefaultStateChangeMinTimeout,
+ }
+
+ _, err = stateConf.WaitForStateContext(ctx)
+ if common.IsCritical(err) {
+ return err
+ }
+
+ return nil
+}
diff --git a/internal/sdkprovider/service/vpc/organization_vpc_data_source.go b/internal/sdkprovider/service/vpc/organization_vpc_data_source.go
new file mode 100644
index 000000000..0c9d7efc9
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/organization_vpc_data_source.go
@@ -0,0 +1,31 @@
+package vpc
+
+import (
+ "context"
+
+ avngen "github.com/aiven/go-client-codegen"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+func DataSourceOrganizationVPC() *schema.Resource {
+ return &schema.Resource{
+ Description: "Gets information about an existing VPC in an Aiven organization.",
+ ReadContext: common.WithGenClient(datasourceOrganizationVPCRead),
+ Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenOrganizationVPCSchema,
+ "organization_id", "organization_vpc_id"),
+ }
+}
+
+func datasourceOrganizationVPCRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
+ var (
+ orgID = d.Get("organization_id").(string)
+ orgVpcID = d.Get("organization_vpc_id").(string)
+ )
+
+ d.SetId(schemautil.BuildResourceID(orgID, orgVpcID))
+
+ return resourceOrganizationVPCRead(ctx, d, client)
+}
diff --git a/internal/sdkprovider/service/vpc/organization_vpc_test.go b/internal/sdkprovider/service/vpc/organization_vpc_test.go
new file mode 100644
index 000000000..43c9dd6f0
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/organization_vpc_test.go
@@ -0,0 +1,178 @@
+package vpc_test
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/plancheck"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+
+ acc "github.com/aiven/terraform-provider-aiven/internal/acctest"
+ "github.com/aiven/terraform-provider-aiven/internal/common"
+ "github.com/aiven/terraform-provider-aiven/internal/schemautil"
+)
+
+const (
+ organizationVPCResource = "aiven_organization_vpc"
+ organizationVPCDataSource = "data_aiven_organization_vpc"
+)
+
+func TestAccAivenOrganizationVPC(t *testing.T) {
+ var (
+ orgName = acc.SkipIfEnvVarsNotSet(t, "AIVEN_ORGANIZATION_NAME")["AIVEN_ORGANIZATION_NAME"]
+ registry = preSetOrgVPCTemplates(t)
+ newComposition = func() *acc.CompositionBuilder {
+ return registry.NewCompositionBuilder().
+ Add("organization_data", map[string]interface{}{
+ "organization_name": orgName})
+ }
+
+ resourceName = fmt.Sprintf("%s.%s", organizationVPCResource, "test_org_vpc")
+ dataSourceName = fmt.Sprintf("data.%s.%s", organizationVPCResource, "vpc_ds")
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckOrganizationVPCResourceDestroy,
+ Steps: []resource.TestStep{
+ {
+ // invalid CIDR range
+ Config: `
+resource "aiven_organization_vpc" "test_validation" {
+ organization_id = "test-org-id"
+ cloud_name = "aws-eu-west-1"
+ network_cidr = "256.256.256.256/24"
+}`,
+ ExpectError: regexp.MustCompile(`expected "network_cidr" to be a valid CIDR Value`),
+ },
+ {
+ // basic VPC creation
+ Config: newComposition().
+ Add(organizationVPCResource, map[string]interface{}{
+ "resource_name": "test_org_vpc",
+ "cloud_name": "aws-eu-west-1",
+ "network_cidr": "10.0.0.0/24",
+ }).MustRender(t),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "cloud_name", "aws-eu-west-1"),
+ resource.TestCheckResourceAttr(resourceName, "network_cidr", "10.0.0.0/24"),
+ resource.TestCheckResourceAttr(resourceName, "state", string(organizationvpc.VpcStateTypeActive)),
+ resource.TestCheckResourceAttrSet(resourceName, "organization_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "create_time"),
+ ),
+ },
+ {
+ // test ForceNew on network_cidr change
+ Config: newComposition().
+ Add(organizationVPCResource, map[string]interface{}{
+ "resource_name": "test_org_vpc",
+ "cloud_name": "aws-eu-west-1",
+ "network_cidr": "10.1.0.0/24", // Changed CIDR
+ }).MustRender(t),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace),
+ },
+ },
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "cloud_name", "aws-eu-west-1"),
+ resource.TestCheckResourceAttr(resourceName, "network_cidr", "10.1.0.0/24"),
+ resource.TestCheckResourceAttr(resourceName, "state", string(organizationvpc.VpcStateTypeActive)),
+ resource.TestCheckResourceAttrSet(resourceName, "organization_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "create_time"),
+ ),
+ },
+ {
+ // import state
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ // test the data source
+ Config: newComposition().
+ Add(organizationVPCResource, map[string]interface{}{
+ "resource_name": "test_org_vpc",
+ "cloud_name": "aws-eu-west-1",
+ "network_cidr": "10.0.0.0/24",
+ }).
+ Add(organizationVPCDataSource, map[string]interface{}{
+ "resource_name": "vpc_ds",
+ "organization_vpc_id": acc.Reference(fmt.Sprintf("%s.organization_vpc_id", resourceName)),
+ }).MustRender(t),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrPair(dataSourceName, "organization_id", resourceName, "organization_id"),
+ resource.TestCheckResourceAttrPair(dataSourceName, "cloud_name", resourceName, "cloud_name"),
+ resource.TestCheckResourceAttrPair(dataSourceName, "network_cidr", resourceName, "network_cidr"),
+ resource.TestCheckResourceAttrPair(dataSourceName, "state", resourceName, "state"),
+ resource.TestCheckResourceAttrPair(dataSourceName, "create_time", resourceName, "create_time"),
+ resource.TestCheckResourceAttr(dataSourceName, "state", string(organizationvpc.VpcStateTypeActive)),
+ resource.TestCheckResourceAttrSet(dataSourceName, "organization_vpc_id"),
+ resource.TestCheckResourceAttrPair(dataSourceName, "organization_vpc_id", resourceName, "organization_vpc_id"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccCheckOrganizationVPCResourceDestroy(s *terraform.State) error {
+ ctx := context.Background()
+
+ c, err := acc.GetTestGenAivenClient()
+ if err != nil {
+ return fmt.Errorf("error initializing Aiven client: %w", err)
+ }
+
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != organizationVPCResource {
+ continue
+ }
+
+ orgID, vpcID, err := schemautil.SplitResourceID2(rs.Primary.ID)
+ if err != nil {
+ return fmt.Errorf("error splitting resource ID: %w", err)
+ }
+
+ orgVPC, err := c.OrganizationVpcGet(ctx, orgID, vpcID)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error fetching VPC (%q): %w", vpcID, err)
+ }
+
+ if orgVPC != nil {
+ return fmt.Errorf("VPC (%q) for organization (%q) still exists", vpcID, orgID)
+ }
+ }
+
+ return nil
+}
+
+func preSetOrgVPCTemplates(t *testing.T) *acc.TemplateRegistry {
+ t.Helper()
+
+ var registry = acc.NewTemplateRegistry(organizationVPCResource)
+
+ registry.MustAddTemplate(t, "organization_data", `
+data "aiven_organization" "foo" {
+ name = "{{ .organization_name }}"
+}`)
+
+ registry.MustAddTemplate(t, organizationVPCResource, `
+resource "aiven_organization_vpc" "{{ .resource_name }}" {
+ organization_id = data.aiven_organization.foo.id
+ cloud_name = "{{ .cloud_name }}"
+ network_cidr = "{{ .network_cidr }}"
+}`)
+
+ registry.MustAddTemplate(t, organizationVPCDataSource, `
+data "aiven_organization_vpc" "{{ .resource_name }}" {
+ organization_id = data.aiven_organization.foo.id
+ organization_vpc_id = {{ if .organization_vpc_id.IsLiteral }}"{{ .organization_vpc_id.Value }}"{{ else }}{{ .organization_vpc_id.Value }}{{ end }}
+}`)
+
+ return registry
+}
diff --git a/internal/sdkprovider/service/vpc/sweep.go b/internal/sdkprovider/service/vpc/sweep.go
index f4462ce18..c33e7c477 100644
--- a/internal/sdkprovider/service/vpc/sweep.go
+++ b/internal/sdkprovider/service/vpc/sweep.go
@@ -40,6 +40,14 @@ func init() {
},
})
+ sweep.AddTestSweepers("aiven_organization_vpc", &resource.Sweeper{
+ Name: "aiven_organization_vpc",
+ F: sweepOrgVPCs(ctx),
+ Dependencies: []string{
+ "aiven_organization",
+ },
+ })
+
sweep.AddTestSweepers("aiven_aws_vpc_peering_connection", &resource.Sweeper{
Name: "aiven_aws_vpc_peering_connection",
F: sweepVPCPeeringCons(ctx),
@@ -48,6 +56,14 @@ func init() {
},
})
+ sweep.AddTestSweepers("aiven_aws_org_vpc_peering_connection", &resource.Sweeper{
+ Name: "aiven_aws_org_vpc_peering_connection",
+ F: sweepOrgVPCPeeringCons(ctx),
+ Dependencies: []string{
+ "aiven_organization_vpc",
+ },
+ })
+
sweep.AddTestSweepers("aiven_azure_vpc_peering_connection", &resource.Sweeper{
Name: "aiven_azure_vpc_peering_connection",
F: sweepVPCPeeringCons(ctx),
@@ -56,6 +72,14 @@ func init() {
},
})
+ sweep.AddTestSweepers("aiven_azure_org_vpc_peering_connection", &resource.Sweeper{
+ Name: "aiven_azure_org_vpc_peering_connection",
+ F: sweepVPCPeeringCons(ctx),
+ Dependencies: []string{
+ "aiven_organization_vpc",
+ },
+ })
+
sweep.AddTestSweepers("aiven_gcp_vpc_peering_connection", &resource.Sweeper{
Name: "aiven_gcp_vpc_peering_connection",
F: sweepVPCPeeringCons(ctx),
@@ -64,6 +88,14 @@ func init() {
},
})
+ sweep.AddTestSweepers("aiven_gcp_org_vpc_peering_connection", &resource.Sweeper{
+ Name: "aiven_gcp_org_vpc_peering_connection",
+ F: sweepVPCPeeringCons(ctx),
+ Dependencies: []string{
+ "aiven_organization_vpc",
+ },
+ })
+
sweep.AddTestSweepers("aiven_transit_gateway_vpc_attachment", &resource.Sweeper{
Name: "aiven_transit_gateway_vpc_attachment",
F: sweepVPCPeeringCons(ctx),
@@ -115,6 +147,41 @@ func sweepVPCs(ctx context.Context) func(string) error {
}
}
+func sweepOrgVPCs(ctx context.Context) func(string) error {
+ return func(_ string) error {
+ orgName := os.Getenv("AIVEN_ORGANIZATION_NAME")
+ client, err := sweep.SharedGenClient()
+ if err != nil {
+ return err
+ }
+
+ list, err := client.AccountList(ctx)
+ if err != nil {
+ return fmt.Errorf("error retrieving a list of organizations : %w", err)
+ }
+
+ for _, org := range list {
+ if org.AccountName != orgName {
+ continue
+ }
+
+ VPCs, err := client.OrganizationVpcList(ctx, org.OrganizationId)
+ if err != nil {
+ return fmt.Errorf("error retrieving a list of vpcs for a organization : %w", err)
+ }
+
+ for _, vpc := range VPCs {
+ _, err := client.OrganizationVpcDelete(ctx, org.OrganizationId, vpc.OrganizationVpcId)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error deleting vpc %s: %w", vpc.OrganizationVpcId, err)
+ }
+ }
+ }
+
+ return nil
+ }
+}
+
func sweepVPCPeeringCons(ctx context.Context) func(string) error {
return func(_ string) error {
projectName := os.Getenv("AIVEN_PROJECT_NAME")
@@ -178,6 +245,58 @@ func sweepVPCPeeringCons(ctx context.Context) func(string) error {
}
}
+func sweepOrgVPCPeeringCons(ctx context.Context) func(string) error {
+ return func(_ string) error {
+ orgName := os.Getenv("AIVEN_ORGANIZATION_NAME")
+ client, err := sweep.SharedGenClient()
+ if err != nil {
+ return err
+ }
+
+ list, err := client.AccountList(ctx)
+ if err != nil {
+ return fmt.Errorf("error retrieving a list of organizations : %w", err)
+ }
+
+ for _, org := range list {
+ if org.AccountName != orgName {
+ continue
+ }
+
+ VPCs, err := client.OrganizationVpcList(ctx, org.OrganizationId)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error retrieving a list of vpcs for a project : %w", err)
+ }
+
+ for _, vpc := range VPCs {
+ orgVPC, err := client.OrganizationVpcGet(ctx, org.OrganizationId, vpc.OrganizationVpcId)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error retrieving a list of vpcs for a project : %w", err)
+ }
+
+ for _, peeringCon := range orgVPC.PeeringConnections {
+ if peeringCon.PeeringConnectionId == nil {
+ continue // should not happen
+ }
+
+ _, err = client.OrganizationVpcPeeringConnectionDeleteById(ctx, org.OrganizationId, vpc.OrganizationVpcId, *peeringCon.PeeringConnectionId)
+ if common.IsCritical(err) {
+ return fmt.Errorf("error deleting vpc peering connection %s/%s/%s: %w",
+ vpc.OrganizationVpcId,
+ vpc.OrganizationVpcId,
+ *peeringCon.PeeringConnectionId,
+ err)
+ }
+
+ }
+
+ }
+ }
+
+ return nil
+ }
+}
+
func sweepAWSPrivatelinks(ctx context.Context) func(string) error {
return func(_ string) error {
projectName := os.Getenv("AIVEN_PROJECT_NAME")
diff --git a/internal/sdkprovider/service/vpc/vpc_peering_connection.go b/internal/sdkprovider/service/vpc/vpc_peering_connection.go
index d8b7a27fa..42b2553d0 100644
--- a/internal/sdkprovider/service/vpc/vpc_peering_connection.go
+++ b/internal/sdkprovider/service/vpc/vpc_peering_connection.go
@@ -109,7 +109,7 @@ func resourceVPCPeeringConnectionCreate(ctx context.Context, d *schema.ResourceD
}
pc = res.(*aiven.VPCPeeringConnection)
- diags := getDiagnosticsFromState(pc)
+ diags := getDiagnosticsFromState(newAivenVPCPeeringState(pc))
if peerRegion != "" {
d.SetId(schemautil.BuildResourceID(projectName, vpcID, pc.PeerCloudAccount, pc.PeerVPC, *pc.PeerRegion))
@@ -125,30 +125,6 @@ func resourceVPCPeeringConnectionCreate(ctx context.Context, d *schema.ResourceD
return append(diags, resourceVPCPeeringConnectionRead(ctx, d, m)...)
}
-// stateInfoToString converts VPC peering connection state_info to a string
-func stateInfoToString(s *map[string]interface{}) string {
- if len(*s) == 0 {
- return ""
- }
-
- var str string
- // Print message first
- if m, ok := (*s)["message"]; ok {
- str = fmt.Sprintf("%s", m)
- delete(*s, "message")
- }
-
- for k, v := range *s {
- if _, ok := v.(string); ok {
- str += fmt.Sprintf("\n %q:%q", k, v)
- } else {
- str += fmt.Sprintf("\n %q:`%+v`", k, v)
- }
- }
-
- return str
-}
-
type peeringVPCID struct {
projectName string
vpcID string
@@ -489,37 +465,6 @@ func isAzureVPCPeeringConnection(ctx context.Context, d *schema.ResourceData, c
return false, nil
}
-func getDiagnosticsFromState(pc *aiven.VPCPeeringConnection) diag.Diagnostics {
- if pc.State != "ACTIVE" {
- switch pc.State {
- case "PENDING_PEER":
- return diag.Diagnostics{{
- Severity: diag.Warning,
- Summary: fmt.Sprintf("Aiven platform has created a connection to the specified "+
- "peer successfully in the cloud, but the connection is not active until the user "+
- "completes the setup in their cloud account. The steps needed in the user cloud "+
- "account depend on the used cloud provider. Find more in the state info: %s",
- stateInfoToString(pc.StateInfo))}}
- case "DELETED":
- return diag.Errorf("A user has deleted the peering connection through the Aiven " +
- "Terraform provider, or Aiven Web Console or directly via Aiven API. There are no " +
- "transitions from this state")
- case "DELETED_BY_PEER":
- return diag.Errorf("A user deleted the peering cloud resource in their account. " +
- "There are no transitions from this state")
- case "REJECTED_BY_PEER":
- return diag.Errorf("VPC peering connection request was rejected, state info: %s",
- stateInfoToString(pc.StateInfo))
- case "INVALID_SPECIFICATION":
- return diag.Errorf("VPC peering connection cannot be created, more in the state info: %s",
- stateInfoToString(pc.StateInfo))
- default:
- return diag.Errorf("Unknown VPC peering connection state: %s", pc.State)
- }
- }
- return nil
-}
-
func validateVPCID(i interface{}, k string) (warnings []string, errors []error) {
v, ok := i.(string)
if !ok {
diff --git a/internal/sdkprovider/service/vpc/vpc_peering_connection_state.go b/internal/sdkprovider/service/vpc/vpc_peering_connection_state.go
new file mode 100644
index 000000000..9e7808bb0
--- /dev/null
+++ b/internal/sdkprovider/service/vpc/vpc_peering_connection_state.go
@@ -0,0 +1,111 @@
+package vpc
+
+import (
+ "fmt"
+
+ "github.com/aiven/aiven-go-client/v2"
+ "github.com/aiven/go-client-codegen/handler/organizationvpc"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+)
+
+// this code provides adapter implementations for VPC peering connection state handling
+// between different HTTP clients (aiven-go-client and go-client-codegen). It defines a common
+// interface peeringConnectionState and wrapper types that adapt different client responses to
+// this interface, enabling state checks and diagnostics without major code refactoring.
+type peeringConnectionState interface {
+ GetState() string
+ GetStateInfo() *map[string]any
+}
+
+func getDiagnosticsFromState(pc peeringConnectionState) diag.Diagnostics {
+ if pc.GetState() != "ACTIVE" {
+ switch pc.GetState() {
+ case "PENDING_PEER":
+ return diag.Diagnostics{{
+ Severity: diag.Warning,
+ Summary: fmt.Sprintf("Aiven platform has created a connection to the specified "+
+ "peer successfully in the cloud, but the connection is not active until the user "+
+ "completes the setup in their cloud account. The steps needed in the user cloud "+
+ "account depend on the used cloud provider. Find more in the state info: %s",
+ stateInfoToString(pc.GetStateInfo()))}}
+ case "DELETED":
+ return diag.Errorf("A user has deleted the peering connection through the Aiven " +
+ "Terraform provider, or Aiven Web Console or directly via Aiven API. There are no " +
+ "transitions from this state")
+ case "DELETED_BY_PEER":
+ return diag.Errorf("A user deleted the peering cloud resource in their account. " +
+ "There are no transitions from this state")
+ case "REJECTED_BY_PEER":
+ return diag.Errorf("VPC peering connection request was rejected, state info: %s",
+ stateInfoToString(pc.GetStateInfo()))
+ case "INVALID_SPECIFICATION":
+ return diag.Errorf("VPC peering connection cannot be created, more in the state info: %s",
+ stateInfoToString(pc.GetStateInfo()))
+ default:
+ return diag.Errorf("Unknown VPC peering connection state: %s", pc.GetState())
+ }
+ }
+ return nil
+}
+
+// stateInfoToString converts VPC peering connection state_info to a string
+func stateInfoToString(s *map[string]interface{}) string {
+ if s == nil || len(*s) == 0 {
+ return ""
+ }
+
+ var str string
+ // Print message first
+ if m, ok := (*s)["message"]; ok {
+ str = fmt.Sprintf("%s", m)
+ delete(*s, "message")
+ }
+
+ for k, v := range *s {
+ if _, ok := v.(string); ok {
+ str += fmt.Sprintf("\n %q:%q", k, v)
+ } else {
+ str += fmt.Sprintf("\n %q:`%+v`", k, v)
+ }
+ }
+
+ return str
+}
+
+type aivenVPCPeeringWrapper struct {
+ *aiven.VPCPeeringConnection
+}
+
+// Create wrapper functions instead of methods
+func newAivenVPCPeeringState(pc *aiven.VPCPeeringConnection) peeringConnectionState {
+ return &aivenVPCPeeringWrapper{pc}
+}
+
+func (w *aivenVPCPeeringWrapper) GetState() string {
+ return w.State
+}
+
+func (w *aivenVPCPeeringWrapper) GetStateInfo() *map[string]any {
+ return w.StateInfo
+}
+
+type organizationVPCPeeringWrapper struct {
+ *organizationvpc.OrganizationVpcGetPeeringConnectionOut
+}
+
+func newOrganizationVPCPeeringState(pc *organizationvpc.OrganizationVpcGetPeeringConnectionOut) *organizationVPCPeeringWrapper {
+ return &organizationVPCPeeringWrapper{pc}
+}
+
+func (w *organizationVPCPeeringWrapper) GetState() string {
+ return string(w.State)
+}
+
+func (w *organizationVPCPeeringWrapper) GetStateInfo() *map[string]any {
+ stateInfo := make(map[string]any)
+
+ stateInfo["message"] = w.StateInfo.Message
+ stateInfo["type"] = w.StateInfo.Type
+
+ return &stateInfo
+}