diff --git a/.changelog/892.txt b/.changelog/892.txt new file mode 100644 index 00000000..fe8e3a16 --- /dev/null +++ b/.changelog/892.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +`datasource/cloudavenue_elb_service_engine_group` - Added a new data source to retrieve information about a Service Engine Group. +``` \ No newline at end of file diff --git a/.changelog/901.txt b/.changelog/901.txt new file mode 100644 index 00000000..f9f57f86 --- /dev/null +++ b/.changelog/901.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +`datasource/cloudavenue_elb_service_engine_groups` - Added a new data source to list all Service Engine Group attached to an Edge Gateway. +``` \ No newline at end of file diff --git a/.changelog/904.txt b/.changelog/904.txt new file mode 100644 index 00000000..e0b744ea --- /dev/null +++ b/.changelog/904.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +`resource/cloudavenue_org_certificate_library` - Added new resource to manage certificate in the Cloud Avenue Organization. +``` + +```release-note:new-data-source +`datasource/cloudavenue_org_certificate_library` - Added new datasource to get certificate information in the Cloud Avenue Organization. +``` \ No newline at end of file diff --git a/.changelog/973.txt b/.changelog/973.txt new file mode 100644 index 00000000..e88a8563 --- /dev/null +++ b/.changelog/973.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +`resource/cloudavenue_elb_virtual_service` - Add new resource `cloudavenue_elb_virtual_service` to manage edgegateway load balancer virtual services. A virtual service advertises an IP address and ports to the external world and listens for client traffic. +``` + +```release-note:new-data-source +`datasource/cloudavenue_elb_virtual_service` - Add new datasource `cloudavenue_elb_virtual_service` to read details of an existing edgegateway load balancer virtual service. +``` \ No newline at end of file diff --git a/.changelog/974.txt b/.changelog/974.txt new file mode 100644 index 00000000..144c9825 --- /dev/null +++ b/.changelog/974.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +`resource/cloudavenue_elb_pool` - Added new resource `cloudavenue_elb_pool` to manage edgegateway load balancer pools. Pools maintain the list of servers assigned to them and perform health monitoring, load balancing, persistence. +``` + +```release-note:new-data-source +`datasource/cloudavenue_elb_pool` - Added new datasource `cloudavenue_elb_pool` to read details of an existing edgegateway load balancer pool. +``` \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 61c96c6f..042d8616 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -50,7 +50,7 @@ "remoteUser": "vscode", "updateRemoteUserUID": true, "features": { - "github-cli": "latest", + "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/terraform:1": {}, "ghcr.io/guiyomh/features/golangci-lint:0": {}, "ghcr.io/meaningful-ooo/devcontainer-features/fish": {}, @@ -59,4 +59,4 @@ }, "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", "workspaceFolder": "/workspace" -} +} \ No newline at end of file diff --git a/.github/workflows/new-release.yaml b/.github/workflows/new-release.yaml deleted file mode 100644 index 40c790d6..00000000 --- a/.github/workflows/new-release.yaml +++ /dev/null @@ -1,201 +0,0 @@ -name: TagRelease - -on: - workflow_dispatch: - inputs: - tag: - description: 'Tag version number (Eg: v0.1.0)' - required: true - type: string - -permissions: - contents: write - -jobs: - # * Step 0: Pre-Check - pre-check: - runs-on: ubuntu-latest - outputs: - TAG_NAME: ${{ steps.set-tag.outputs.TAG_NAME }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ secrets.CHANGELOG_PAT }} - - name: Check if tag is valid - run : | - # Check if the tag start with 'v', if not, add it - if [[ ! ${{ github.event.inputs.tag }} =~ ^v.* ]]; then - echo "Error tag format is invalid. The format is vx.x.x" >> "$GITHUB_OUTPUT" - exit 1 - fi - - name: Construct Tag for Pre-Release - id: set-tag - run: | - # Construct the tag name - echo "TAG_NAME=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" - # * Step 1: Check if everything is ok - tag-already-exist: - needs: [pre-check] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ secrets.CHANGELOG_PAT }} - - name: Check if tag not already exists - run: | - if git rev-parse ${{ needs.pre-check.outputs.TAG_NAME }} >/dev/null 2>&1; then - echo "Tag ${{ needs.pre-check.outputs.TAG_NAME }} already exists" >> "$GITHUB_OUTPUT" - exit 1 - fi - golangci-lint: - needs: [pre-check] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.CHANGELOG_PAT }} - ref: ${{ github.ref }} - - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - run: go mod download - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: latest - testsunit: - needs: [pre-check] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.CHANGELOG_PAT }} - ref: ${{ github.ref }} - - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - run: go mod download - - name: Run Go unit tests - env: - TEST_CLOUDAVENUE_ORG: ${{ secrets.CLOUDAVENUE_ORG }} - TEST_CLOUDAVENUE_USERNAME: ${{ secrets.CLOUDAVENUE_USER }} - TEST_CLOUDAVENUE_PASSWORD: ${{ secrets.CLOUDAVENUE_PASSWORD }} - TEST_CLOUDAVENUE_VDC: ${{ secrets.CLOUDAVENUE_VDC }} - run: | - go test $(go list ./... | grep -v /internal/testsacc/) - - # * Step 2: Create a new tag - tag: - needs: [golangci-lint, pre-check, tag-already-exist, testsunit] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.CHANGELOG_PAT }} - - uses: rickstaa/action-create-tag@v1 - id: "tag_create" - with: - tag: ${{ needs.pre-check.outputs.TAG_NAME }} - tag_exists_error: true - message: "Release ${{ needs.pre-check.outputs.TAG_NAME }}" - - release-notes: - runs-on: ubuntu-latest - needs: [tag, pre-check] - steps: - - uses: actions/checkout@v4 # v3.5.3 - with: - fetch-depth: 0 - ref: ${{ needs.pre-check.outputs.TAG_NAME }} - - name: Generate Release Notes - run: | - echo "" > release-notes.txt - export PREV_TAG=$(git tag --list 'v*' --sort=-version:refname | grep -E "v[0-9]+\.[0-9]+\.[0-9]+$" | head -n 2 | tail -n 1) - export PREV_VERSION=${PREV_TAG//v} - sed -n -e "1{/# /d;}" -e "2{/^$/d;}" -e "/# $PREV_VERSION/q;p" CHANGELOG.md >> release-notes.txt - - uses: actions/upload-artifact@v4 - with: - name: release-notes - path: release-notes.txt - retention-days: 1 - release-app: - runs-on: ubuntu-latest - needs: [release-notes, pre-check] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ needs.pre-check.outputs.TAG_NAME }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0 - id: import_gpg - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.PASSPHRASE }} - - id: release-notes-download - name: Release Notes Download - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: release-notes - path: /tmp - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6.2.1 - if: success() - with: - distribution: goreleaser - version: latest - args: release --clean -f .goreleaser.yml --release-notes=${{ steps.release-notes-download.outputs.download-path }}/release-notes.txt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - METRICS_TARGET: ${{ secrets.METRICS_TARGET }} - METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }} - changelog-newversion: - needs: [release-app, pre-check] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 # v3.5.3 - with: - token: ${{ secrets.CHANGELOG_PAT }} - fetch-depth: 0 - ref: main - - name: Update Changelog Header - run: | - CHANGELOG_FILE_NAME="CHANGELOG.md" - RELEASE_TAG=${{ needs.pre-check.outputs.TAG_NAME }} - # PREVIOUS_RELEASE_TAG=${{ github.ref_name }} - - # Add Release Date - RELEASE_DATE=`date +%B' '%e', '%Y` - sed -i -e "1 s/.*Unreleased.*/## ${RELEASE_TAG#v} ($RELEASE_DATE)/" $CHANGELOG_FILE_NAME - - # Prepend next release line - echo Release is: $RELEASE_TAG - - NEW_RELEASE_LINE=$(echo $RELEASE_TAG | awk -F. '{ - $1 = substr($1,2) - $2 += 1 - printf("%s.%01d.0\n\n", $1, $2); - }') - - echo New minor version is: v$NEW_RELEASE_LINE - - echo -e "## $NEW_RELEASE_LINE (Unreleased)\n$(cat $CHANGELOG_FILE_NAME)" > $CHANGELOG_FILE_NAME - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore: Update CHANGELOG.md after ${{ github.ref_name }}" - commit_options: '--no-verify --signoff' - file_pattern: CHANGELOG.md - commit_user_name: github-actions[bot] - commit_user_email: github-actions[bot]@users.noreply.github.com - commit_author: github-actions[bot] - diff --git a/docs/data-sources/alb_pool.md b/docs/data-sources/alb_pool.md deleted file mode 100644 index 471793d7..00000000 --- a/docs/data-sources/alb_pool.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -page_title: "cloudavenue_alb_pool Data Source - cloudavenue" -subcategory: "ALB (Advanced Load Balancer)" -description: |- - Provides a data source to manage Advanced Load Balancer Pools. Pools maintain the list of assigned servers and perform health monitoring, load balancing, and persistence. ---- - -# cloudavenue_alb_pool (Data Source) - -Provides a data source to manage Advanced Load Balancer Pools. Pools maintain the list of assigned servers and perform health monitoring, load balancing, and persistence. - -## Example Usage - -```terraform -data "cloudavenue_alb_pool" "example" { - edge_gateway_name = "MyEdgeGatewayName" - name = "MyAlbPoolName" -} -``` - - -## Schema - -### Required - -- `name` (String) Name of ALB Pool. - -### Optional - -- `edge_gateway_id` (String) Edge gateway ID in which ALB Pool was created. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. -- `edge_gateway_name` (String) Edge gateway Name in which ALB Pool was created. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. - -### Read-Only - -- `algorithm` (String) Algorithm for selecting members within a pool. -- `default_port` (Number) Destination server port used for traffic sent to a member. -- `description` (String) Description of ALB Pool. -- `enabled` (Boolean) Define if ALB Pool is enabled or not. -- `graceful_timeout_period` (Number) Maximum time in minutes allowed for gracefully disabling a pool member. -- `health_monitors` (Set of String) List of health monitors type to activate. -- `id` (String) ID of ALB Pool. -- `members` (Attributes Set) ALB Pool Member(s). (see [below for nested schema](#nestedatt--members)) -- `passive_monitoring_enabled` (Boolean) Monitors if the traffic is accepted by node. -- `persistence_profile` (Attributes) Persistence profile ensures that a user remains connected to the same server for a specified duration. If the persistence profile is unmanaged by Cloud Avenue, updates with unchanged values will continue using the same unmanaged profile. However, any changes to the persistence profile will prompt Cloud Avenue to switch the pool to a profile it manages. (see [below for nested schema](#nestedatt--persistence_profile)) - - -### Nested Schema for `members` - -Read-Only: - -- `enabled` (Boolean) Indicates whether a pool member accepts traffic. -- `ip_address` (String) IP address of a pool member. -- `port` (Number) Member port. -- `ratio` (Number) Ratio of selecting eligible servers in the pool. - - - -### Nested Schema for `persistence_profile` - -Read-Only: - -- `type` (String) Type of persistence strategy. -- `value` (String) Value of attribute based on persistence type. - diff --git a/docs/data-sources/elb_pool.md b/docs/data-sources/elb_pool.md new file mode 100644 index 00000000..0a67a441 --- /dev/null +++ b/docs/data-sources/elb_pool.md @@ -0,0 +1,93 @@ +--- +page_title: "cloudavenue_elb_pool Data Source - cloudavenue" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- + The cloudavenue_elb_pool data source allows you to retrieve information about an existing edgegateway load balancer pool. +--- + +# cloudavenue_elb_pool (Data Source) + +The `cloudavenue_elb_pool` data source allows you to retrieve information about an existing edgegateway load balancer pool. + +## Example Usage + +```terraform +data "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = cloudavenue_edge_gateway.example.id +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the pool. + +### Optional + +- `edge_gateway_id` (String) The ID of the Edge Gateway. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. +- `edge_gateway_name` (String) The name of the Edge Gateway. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. + +### Read-Only + +- `algorithm` (String) The heart of a load balancer is its ability to effectively distribute traffic across healthy servers. If persistence is enabled, only the first connection from a client is load balanced. While the persistence remains in effect, subsequent connections or requests from a client are directed to the same server. +- `default_port` (Number) DefaultPort defines destination server port used by the traffic sent to the member. +- `description` (String) The name of the pool. +- `enabled` (Boolean) Enable or disable the pool. +- `health` (Attributes) . (see [below for nested schema](#nestedatt--health)) +- `id` (String) The ID of the pool. +- `members` (Attributes) . (see [below for nested schema](#nestedatt--members)) +- `persistence` (Attributes) . (see [below for nested schema](#nestedatt--persistence)) +- `tls` (Attributes) . (see [below for nested schema](#nestedatt--tls)) + + +### Nested Schema for `health` + +Read-Only: + +- `monitors` (List of String) The active health monitors. +- `passive_monitoring_enabled` (Boolean) PassiveMonitoringEnabled sets if client traffic should be used to check if pool member is up or down. + + + +### Nested Schema for `members` + +Read-Only: + +- `graceful_timeout_period` (String) Maximum time (in minutes) to gracefully disable a member. Virtual service waits for the specified time before terminating the existing connections to the members that are disabled. Special values: `0` represents `Immediate` and `-1` represents `Infinite`. The maximum value is `7200` minutes. +- `target_group` (String) The group contains reference to the Edge Firewall Group representing destination servers which are used by the Load Balancer Pool to direct load balanced traffic. This permit to reference `IP Set` or `Static Group` ID. +- `targets` (Attributes List) targets field defines list of destination servers which are used by the Load Balancer Pool to direct load balanced traffic. (see [below for nested schema](#nestedatt--members--targets)) + + +### Nested Schema for `members.targets` + +Read-Only: + +- `enabled` (Boolean) Enable or disable the member. +- `ip_address` (String) The IP address of the member. +- `port` (Number) The port of the member. +- `ratio` (Number) The ratio of the member. The ratio of each pool member denotes the traffic that goes to each server pool member. A server with a ratio of 2 gets twice as much traffic as a server with a ratio of 1. + + + + +### Nested Schema for `persistence` + +Read-Only: + +- `type` (String) The type of the persistence. +- `value` (String) The value of the persistence. + + + +### Nested Schema for `tls` + +Read-Only: + +- `ca_certificate_refs` (List of String) The CA certificate references point to root certificates to use when validating certificates presented by the pool members. +- `common_name_check_enabled` (Boolean) Enable common name check for server certificate. If enabled and no explicit domain name is specified, the incoming host header will be used to do the match. +- `domain_names` (List of String) The domain names of the TLS check. This attribute is taken into account if the `common_name_check_enabled` is set to `true`. +- `enabled` (Boolean) Enable or disable the TLS. + diff --git a/docs/data-sources/elb_service_engine_group.md b/docs/data-sources/elb_service_engine_group.md new file mode 100644 index 00000000..acd7183c --- /dev/null +++ b/docs/data-sources/elb_service_engine_group.md @@ -0,0 +1,39 @@ +--- +page_title: "cloudavenue_elb_service_engine_group Data Source - cloudavenue" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- + The cloudavenue_elb_service_engine_group data source allows you to retrieve information about an Service Engine Group of an Edge Gateway. +--- + +# cloudavenue_elb_service_engine_group (Data Source) + +The `cloudavenue_elb_service_engine_group` data source allows you to retrieve information about an Service Engine Group of an Edge Gateway. + +## Example Usage + +```terraform +data "cloudavenue_elb_service_engine_group" "example" { + name = "my-service-engine" + edge_gateway_name = data.cloudavenue_edge_gateway.example.name +} + +output "example" { + value = data.cloudavenue_elb_service_engine_group.example +} +``` + + +## Schema + +### Optional + +- `edge_gateway_id` (String) Edge gateway ID in which ELB Service Engine Group should be located. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. +- `edge_gateway_name` (String) Edge gateway Name in which ELB Service Engine Group should be located. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. +- `id` (String) The ID of the ELB Service Engine Group. Ensure that one and only one attribute from this collection is set : `id`, `name`. +- `name` (String) The name of the ELB Service Engine Group. Ensure that one and only one attribute from this collection is set : `id`, `name`. + +### Read-Only + +- `deployed_virtual_services` (Number) The number of deployed virtual services on the ELB Service Engine Group. +- `max_virtual_services` (Number) The maximum number of virtual services that can be deployed on the ELB Service Engine Group. +- `reserved_virtual_services` (Number) The number of reserved virtual services for the ELB Service Engine Group. diff --git a/docs/data-sources/elb_service_engine_groups.md b/docs/data-sources/elb_service_engine_groups.md new file mode 100644 index 00000000..69baccfb --- /dev/null +++ b/docs/data-sources/elb_service_engine_groups.md @@ -0,0 +1,51 @@ +--- +page_title: "cloudavenue_elb_service_engine_groups Data Source - cloudavenue" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- + The cloudavenue_elb_service_engine_groups data source allows you to retrieve information about all the Service Engine Group of an Edge Gateway. +--- + +# cloudavenue_elb_service_engine_groups (Data Source) + +The `cloudavenue_elb_service_engine_groups` data source allows you to retrieve information about all the Service Engine Group of an Edge Gateway. + +## Example Usage + +```terraform +data "cloudavenue_elb_service_engine_groups" "example" { + edge_gateway_name = data.cloudavenue_edge_gateway.example.name +} + +output "example" { + value = data.cloudavenue_elb_service_engine_groups.example +} +``` + + +## Schema + +### Optional + +- `edge_gateway_id` (String) Edge gateway ID in which EdgeGateway LoadBalancer Service Engine Group should be located. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. +- `edge_gateway_name` (String) Edge gateway Name in which EdgeGateway LoadBalancer Service Engine Group should be located. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. + +### Read-Only + +- `id` (String) The ID of the ELB service engine groups. +- `service_engine_groups` (Attributes List) The list of service engine groups. (see [below for nested schema](#nestedatt--service_engine_groups)) + + +### Nested Schema for `service_engine_groups` + +Optional: + +- `edge_gateway_id` (String) Edge gateway ID in which ELB Service Engine Group should be located. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. +- `edge_gateway_name` (String) Edge gateway Name in which ELB Service Engine Group should be located. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. +- `id` (String) The ID of the ELB Service Engine Group. Ensure that one and only one attribute from this collection is set : `id`, `name`. +- `name` (String) The name of the ELB Service Engine Group. Ensure that one and only one attribute from this collection is set : `id`, `name`. + +Read-Only: + +- `deployed_virtual_services` (Number) The number of deployed virtual services on the ELB Service Engine Group. +- `max_virtual_services` (Number) The maximum number of virtual services that can be deployed on the ELB Service Engine Group. +- `reserved_virtual_services` (Number) The number of reserved virtual services for the ELB Service Engine Group. diff --git a/docs/data-sources/elb_virtual_service.md b/docs/data-sources/elb_virtual_service.md new file mode 100644 index 00000000..c7cf41ae --- /dev/null +++ b/docs/data-sources/elb_virtual_service.md @@ -0,0 +1,53 @@ +--- +page_title: "cloudavenue_elb_virtual_service Data Source - cloudavenue" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- + Provides a data source to read ELB Virtual services for particular Gateway. A virtual service advertises an IP address and ports to the external world and listens for client traffic. When a virtual service receives traffic, it directs it to members in ELB Pool. +--- + +# cloudavenue_elb_virtual_service (Data Source) + +Provides a data source to read ELB Virtual services for particular Gateway. A virtual service advertises an IP address and ports to the external world and listens for client traffic. When a virtual service receives traffic, it directs it to members in ELB Pool. + +## Example Usage + +```terraform +data "cloudavenue_elb_virtual_service" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the ELB Virtual Service. + +### Optional + +- `edge_gateway_id` (String) The ID of the edge gateway on which the ELB Virtual Service is to be created. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. +- `edge_gateway_name` (String) The name of the edge gateway on which the ELB Virtual Service is to be created. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. + +### Read-Only + +- `certificate_id` (String) The ID of the certificate. The certificate must be uploaded to your certificate library before it can be used. The certificate MUSTN'T be expired. +- `description` (String) The description of the ELB Virtual Service. +- `enabled` (Boolean) Defines if the ELB Virtual Service is enabled. +- `id` (String) The ID of the ELB virtual service. +- `pool_id` (String) The ID of the ELB Server Pool associated. +- `pool_name` (String) The name of the ELB Server Pool associated. +- `service_engine_group_name` (String) The name of the service Engine Group (Take the first one if not specified). +- `service_ports` (Attributes List) The service port of the ELB Virtual Service. The service port is the port on which the virtual service listens for client traffic. (see [below for nested schema](#nestedatt--service_ports)) +- `service_type` (String) The type of the service. The different modes that the ELB supports for handling TCP traffic and various parameters that can be tuned for optimization of the TCP traffic are also detailed here. +- `virtual_ip` (String) The virtual IP address of the ELB Virtual Service. + + +### Nested Schema for `service_ports` + +Read-Only: + +- `end` (Number) The end port of the service port range. If not specified, only the `start` value is used. +- `start` (Number) The start port of the service port range or exact port number if `end` is not set. + diff --git a/docs/data-sources/org_certificate_library.md b/docs/data-sources/org_certificate_library.md new file mode 100644 index 00000000..559f3225 --- /dev/null +++ b/docs/data-sources/org_certificate_library.md @@ -0,0 +1,31 @@ +--- +page_title: "cloudavenue_org_certificate_library Data Source - cloudavenue" +subcategory: "Organization" +description: |- + The cloudavenue_org_certificate_library data source allows you to retrieve information about an certificate in your organization's library. +--- + +# cloudavenue_org_certificate_library (Data Source) + +The `cloudavenue_org_certificate_library` data source allows you to retrieve information about an certificate in your organization's library. + +## Example Usage + +```terraform +data "cloudavenue_org_certificate_library" "example" { + name = "my-certificate-library" +} +``` + + +## Schema + +### Optional + +- `id` (String) The ID of the certificate library. Ensure that one and only one attribute from this collection is set : `name`, `id`. +- `name` (String) The name of the certificate library. Ensure that one and only one attribute from this collection is set : `name`, `id`. + +### Read-Only + +- `certificate` (String) The certificate content. It can be a PEM encoded certificate or a certificate chain. +- `description` (String) The description of the certificate library. diff --git a/docs/resources/alb_pool.md b/docs/resources/alb_pool.md deleted file mode 100644 index 6e399014..00000000 --- a/docs/resources/alb_pool.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -page_title: "cloudavenue_alb_pool Resource - cloudavenue" -subcategory: "ALB (Advanced Load Balancer)" -description: |- - Provides a resource to manage Advanced Load Balancer Pools. Pools maintain the list of assigned servers and perform health monitoring, load balancing, and persistence. A pool may be used or referenced by only one virtual service at a time. ---- - -# cloudavenue_alb_pool (Resource) - -Provides a resource to manage Advanced Load Balancer Pools. Pools maintain the list of assigned servers and perform health monitoring, load balancing, and persistence. A pool may be used or referenced by only one virtual service at a time. - -## Example Usage - -```terraform -data "cloudavenue_tier0_vrfs" "example" {} - -resource "cloudavenue_edgegateway" "example" { - owner_name = "MyVDC" - tier0_vrf_name = data.cloudavenue_tier0_vrfs.example.names.0 - owner_type = "vdc" - lb_enabled = true -} - -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = cloudavenue_edgegateway.example.name - name = "Example" - - persistence_profile = { - type = "CLIENT_IP" - } - - members = [ - { - ip_address = "192.168.1.1" - port = "80" - }, - { - ip_address = "192.168.1.2" - port = "80" - }, - { - ip_address = "192.168.1.3" - port = "80" - } - ] - - health_monitors = ["UDP", "TCP"] -} - -data "cloudavenue_tier0_vrfs" "example" {} - -resource "cloudavenue_edgegateway" "example" { - owner_name = "MyVDC" - tier0_vrf_name = data.cloudavenue_tier0_vrfs.example.names.0 - owner_type = "vdc" - lb_enabled = true -} - -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = cloudavenue_edgegateway.example.name - name = "Example" -} -``` - - -## Schema - -### Required - -- `name` (String) Name of ALB Pool. - -### Optional - -- `algorithm` (String) Algorithm for selecting members within a pool. Value must be one of : `ROUND_ROBIN`, `CONSISTENT_HASH`, `LEAST_CONNECTIONS`. Value defaults to `LEAST_CONNECTIONS`. -- `default_port` (Number) Destination server port used for traffic sent to a member. Value must be between 1 and 65535. Value defaults to `80`. -- `description` (String) Description of ALB Pool. -- `edge_gateway_id` (String) (ForceNew) Edge gateway ID in which ALB Pool should be created. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. -- `edge_gateway_name` (String) (ForceNew) Edge gateway Name in which ALB Pool should be created. Ensure that one and only one attribute from this collection is set : `edge_gateway_id`, `edge_gateway_name`. -- `enabled` (Boolean) Define if ALB Pool is enabled or not. Value defaults to `true`. -- `graceful_timeout_period` (Number) Maximum time in minutes allowed for gracefully disabling a pool member. Value defaults to `1`. -- `health_monitors` (Set of String) List of health monitors type to activate. Element value must satisfy all validations: value must be one of: ["HTTP" "HTTPS" "TCP" "UDP" "PING"]. -- `members` (Attributes Set) ALB Pool Member(s). (see [below for nested schema](#nestedatt--members)) -- `passive_monitoring_enabled` (Boolean) Monitors if the traffic is accepted by node. Value defaults to `true`. -- `persistence_profile` (Attributes) Persistence profile ensures that a user remains connected to the same server for a specified duration. If the persistence profile is unmanaged by Cloud Avenue, updates with unchanged values will continue using the same unmanaged profile. However, any changes to the persistence profile will prompt Cloud Avenue to switch the pool to a profile it manages. (see [below for nested schema](#nestedatt--persistence_profile)) - -### Read-Only - -- `id` (String) ID of ALB Pool. - - -### Nested Schema for `members` - -Required: - -- `ip_address` (String) IP address of a pool member. Must be a valid IP with net.ParseIP. -- `port` (Number) Member port. Value must be between 1 and 65535. - -Optional: - -- `enabled` (Boolean) Indicates whether a pool member accepts traffic. Value defaults to `true`. -- `ratio` (Number) Ratio of selecting eligible servers in the pool. Value must be at least 1. Value defaults to `1`. - - - -### Nested Schema for `persistence_profile` - -Required: - -- `type` (String) Type of persistence strategy. Value must be one of : `CLIENT_IP`, `HTTP_COOKIE`. - -Optional: - -- `value` (String) Value of attribute based on persistence type. If the value of [`persistence_profile.type`](#persistence_profile.type) attribute is `HTTP_COOKIE` this attribute is **REQUIRED**. - -## Import - -Import is supported using the following syntax: -```shell -# use the edge_gateway_name.alb_pool_name to import the ALB Pool -terraform import cloudavenue_alb_pool.test edge_gateway_name.alb_pool_name -``` \ No newline at end of file diff --git a/docs/resources/elb_pool.md b/docs/resources/elb_pool.md new file mode 100644 index 00000000..893963a9 --- /dev/null +++ b/docs/resources/elb_pool.md @@ -0,0 +1,238 @@ +--- +page_title: "cloudavenue_elb_pool Resource - cloudavenue" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- + The cloudavenue_elb_pool resource allows you to manage edgegateway load balancer pools. Pools maintain the list of servers assigned to them and perform health monitoring, load balancing, persistence. A pool may only be used or referenced by only one virtual service at a time. +--- + +# cloudavenue_elb_pool (Resource) + + ~> **SUBSCRIBE REQUIRED** This resource require to subscribe to the Load Balancer service. Please open a ticket to the support team to enable the service. + +The `cloudavenue_elb_pool` resource allows you to manage edgegateway load balancer pools. Pools maintain the list of servers assigned to them and perform health monitoring, load balancing, persistence. A pool may only be used or referenced by only one virtual service at a time. + +## Example Usage + +Basic working example: + +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } +} +``` + + -> More examples can be found at the [Advanced Usage](#advanced-usage) section. + + + + +## Schema + +### Required + +- `default_port` (Number) DefaultPort defines destination server port used by the traffic sent to the member. +- `members` (Attributes) . (see [below for nested schema](#nestedatt--members)) +- `name` (String) The name of the pool. + +### Optional + +- `algorithm` (String) The heart of a load balancer is its ability to effectively distribute traffic across healthy servers. If persistence is enabled, only the first connection from a client is load balanced. While the persistence remains in effect, subsequent connections or requests from a client are directed to the same server. Value must be one of: `CONSISTENT_HASH` (New connections are distributed across the servers by using the IP address of the client to generate an IP hash.), `CORE_AFFINITY` (Each CPU core uses a subset of servers, and each server is used by a subset of cores. Essentially it provides a many-to-many mapping between servers and cores.), `FASTEST_RESPONSE` (New connections are sent to the server that is currently providing the fastest response to new connections or requests.), `FEWEST_SERVERS` (Instead of attempting to distribute all connections or requests across all servers, the fewest number of servers which are required to satisfy the current client load will be determined.), `FEWEST_TASKS` (Load is adaptively balanced, based on server feedback.), `LEAST_CONNECTIONS` (New connections are sent to the server that currently has the least number of outstanding concurrent connections.), `LEAST_LOAD` (New connections are sent to the server with the lightest load, regardless of the number of connections that server has.), `RANDOM` (Picks servers at random), `ROUND_ROBIN` (New connections are sent to the next eligible server in the pool in sequential order.). Value defaults to `LEAST_CONNECTIONS`. +- `description` (String) The name of the pool. +- `edge_gateway_id` (String) (ForceNew) The ID of the Edge Gateway. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. +- `edge_gateway_name` (String) (ForceNew) The name of the Edge Gateway. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. +- `enabled` (Boolean) Enable or disable the pool. Value defaults to `true`. +- `health` (Attributes) . Value defaults to `{"monitors":,"passive_monitoring_enabled":true}`. (see [below for nested schema](#nestedatt--health)) +- `persistence` (Attributes) . Value defaults to `{"type":"CLIENT_IP","value":}`. (see [below for nested schema](#nestedatt--persistence)) +- `tls` (Attributes) . Value defaults to `{"ca_certificate_refs":,"common_name_check_enabled":false,"domain_names":,"enabled":false}`. (see [below for nested schema](#nestedatt--tls)) + +### Read-Only + +- `id` (String) The ID of the pool. + + +### Nested Schema for `members` + +Optional: + +- `graceful_timeout_period` (String) Maximum time (in minutes) to gracefully disable a member. Virtual service waits for the specified time before terminating the existing connections to the members that are disabled. Special values: `0` represents `Immediate` and `-1` represents `Infinite`. The maximum value is `7200` minutes. Value defaults to `1`. +- `target_group` (String) The group contains reference to the Edge Firewall Group representing destination servers which are used by the Load Balancer Pool to direct load balanced traffic. This permit to reference `IP Set` or `Static Group` ID. Ensure that one and only one attribute from this collection is set : `targets`, `target_group`. +- `targets` (Attributes List) targets field defines list of destination servers which are used by the Load Balancer Pool to direct load balanced traffic. Ensure that one and only one attribute from this collection is set : `targets`, `target_group`. (see [below for nested schema](#nestedatt--members--targets)) + + +### Nested Schema for `members.targets` + +Required: + +- `ip_address` (String) The IP address of the member. The value must be a valid IPV4 address (`192.168.0.1`). +- `port` (Number) The port of the member. + +Optional: + +- `enabled` (Boolean) Enable or disable the member. Value defaults to `true`. +- `ratio` (Number) The ratio of the member. The ratio of each pool member denotes the traffic that goes to each server pool member. A server with a ratio of 2 gets twice as much traffic as a server with a ratio of 1. Value defaults to `1`. + + + + +### Nested Schema for `health` + +Optional: + +- `monitors` (List of String) The active health monitors. Element value must satisfy all validations: value must be one of: ["HTTP" "HTTPS" "PING" "TCP" "UDP"]. +- `passive_monitoring_enabled` (Boolean) PassiveMonitoringEnabled sets if client traffic should be used to check if pool member is up or down. Value defaults to `true`. + + + +### Nested Schema for `persistence` + +Optional: + +- `type` (String) The type of the persistence. Value must be one of: `APP_COOKIE` (Load Balancer reads existing server cookies or URI embedded data such as JSessionID. Cookie name must be provided as value.), `CLIENT_IP` (The clients IP is used as the identifier and mapped to the server.), `CUSTOM_HTTP_HEADER` (Custom, static mappings of header values to specific servers are used. Header name must be provided as value.), `HTTP_COOKIE` (Load Balancer inserts a cookie into HTTP responses. Cookie name must be provided as value.), `TLS` (Information is embedded in the client's SSL/TLS ticket ID. This will use default system profile System-Persistence-TLS.). Value defaults to `CLIENT_IP`. +- `value` (String) The value of the persistence. If the value of [`<.type`](#<.type) attribute is one of `HTTP_COOKIE`, `CUSTOM_HTTP_HEADER` or `APP_COOKIE` this attribute is **REQUIRED**. + + + +### Nested Schema for `tls` + +Optional: + +- `ca_certificate_refs` (List of String) The CA certificate references point to root certificates to use when validating certificates presented by the pool members. Use `cloudavenue_org_certificate` resource to create a certificate and get the ID. Element value must satisfy all validations: must start with "urn:vcloud:certificateLibraryItem:". +- `common_name_check_enabled` (Boolean) Enable common name check for server certificate. If enabled and no explicit domain name is specified, the incoming host header will be used to do the match. Value defaults to `false`. +- `domain_names` (List of String) The domain names of the TLS check. This attribute is taken into account if the `common_name_check_enabled` is set to `true`. List must contain at least 0 elements and at most 10 elements. +- `enabled` (Boolean) Enable or disable the TLS. Value defaults to `false`. + +## Advanced Usage + +### Multiple Members and health monitors +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + algorithm = "ROUND_ROBIN" + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + }, + { + ip_address = "192.168.0.2" + port = 80 + } + ] + } + health = { + monitors = ["HTTP", "TCP"] + } +} +``` + +### Setting TLS configuration +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + } +} +``` + +### Use IPSet for members +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + members = { + graceful_timeout_period = 2 + target_group = cloudavenue_edgegateway_ip_set.example.id + } +} +``` + +### Full configuration +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + algorithm = "ROUND_ROBIN" + members = { + graceful_timeout_period = 2 + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + enabled = true + ratio = 1 + }, + { + ip_address = "192.168.0.2" + port = 80 + enabled = true + ratio = 1 + }, + { + ip_address = "192.168.0.10" + port = 8080 + enabled = true + ratio = 10 + } + ] + } + + health = { + monitors = ["HTTP", "TCP"] + passive_monitoring_enabled = true + } + + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + common_name_check_enabled = true + } + + persistence = { + type = "CUSTOM_HTTP_HEADER" + value = "X-Custom" + } +} +``` + +## Import + +Import is supported using the following syntax: +```shell +terraform import cloudavenue_elb_pool.example edgeGatewayNameOrID.poolNameOrID +``` \ No newline at end of file diff --git a/docs/resources/elb_virtual_service.md b/docs/resources/elb_virtual_service.md new file mode 100644 index 00000000..360f8998 --- /dev/null +++ b/docs/resources/elb_virtual_service.md @@ -0,0 +1,148 @@ +--- +page_title: "cloudavenue_elb_virtual_service Resource - cloudavenue" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- + Provides a resource to manage ELB Virtual services for particular Gateway. A virtual service advertises an IP address and ports to the external world and listens for client traffic. When a virtual service receives traffic, it directs it to members in ELB Pool. +--- + +# cloudavenue_elb_virtual_service (Resource) + + ~> **SUBSCRIBE REQUIRED** This resource require to subscribe to the Load Balancer service. Please open a ticket to the support team to enable the service. + +Provides a resource to manage ELB Virtual services for particular Gateway. A virtual service advertises an IP address and ports to the external world and listens for client traffic. When a virtual service receives traffic, it directs it to members in ELB Pool. + +## Example Usage + +```terraform +resource "cloudavenue_elb_virtual_service" "example" { + name = "example" + enabled = true + + virtual_ip = "192.168.0.1" + + pool_id = cloudavenue_elb_pool.example.id + edge_gateway_id = cloudavenue_edgegateway.example.id + + service_type = "HTTP" + service_ports = [ + { + start = 80 + } + ] +} +``` + -> More examples can be found at the [Advanced Usage](#advanced-usage) section. + + + +## Schema + +### Required + +- `name` (String) The name of the ELB Virtual Service. +- `service_ports` (Attributes List) The service port of the ELB Virtual Service. The service port is the port on which the virtual service listens for client traffic. (see [below for nested schema](#nestedatt--service_ports)) +- `service_type` (String) The type of the service. The different modes that the ELB supports for handling TCP traffic and various parameters that can be tuned for optimization of the TCP traffic are also detailed here. Value must be one of : `HTTP`, `HTTPS`, `L4_TCP`, `L4_UDP`, `L4_TLS`. +- `virtual_ip` (String) The virtual IP address of the ELB Virtual Service. + + -> The `virtual_ip` accept a private IP in your network range or a public IP (Warning: the public IP must be reserved and not used by any other service). The value must be a valid IPV4 address (`192.168.0.1`). + +### Optional + +- `certificate_id` (String) The ID of the certificate. The certificate must be uploaded to your certificate library before it can be used. The certificate MUSTN'T be expired. If the value of [`<.service_type`](#<.service_type) attribute is one of `L4_TLS` or `HTTPS` this attribute is **REQUIRED**. If the value of [`<.service_type`](#<.service_type) attribute is one of `HTTP`, `L4_TCP` or `L4_UDP` this attribute is **NULL**. This value must start with `urn:vcloud:certificateLibraryItem:`. +- `description` (String) The description of the ELB Virtual Service. +- `edge_gateway_id` (String) (ForceNew) The ID of the edge gateway on which the ELB Virtual Service is to be created. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. +- `edge_gateway_name` (String) (ForceNew) The name of the edge gateway on which the ELB Virtual Service is to be created. Ensure that one and only one attribute from this collection is set : `edge_gateway_name`, `edge_gateway_id`. +- `enabled` (Boolean) Defines if the ELB Virtual Service is enabled. Value defaults to `true`. +- `pool_id` (String) The ID of the ELB Server Pool associated. Ensure that one and only one attribute from this collection is set : `pool_name`, `pool_id`. +- `pool_name` (String) The name of the ELB Server Pool associated. Ensure that one and only one attribute from this collection is set : `pool_name`, `pool_id`. +- `service_engine_group_name` (String) The name of the service Engine Group (Take the first one if not specified). + +### Read-Only + +- `id` (String) The ID of the ELB virtual service. + + +### Nested Schema for `service_ports` + +Required: + +- `start` (Number) The start port of the service port range or exact port number if `end` is not set. Value must be between 1 and 65535. + +Optional: + +- `end` (Number) The end port of the service port range. If not specified, only the `start` value is used. Value must be between 1 and 65535. + +## Advanced Usage + +### Expose TLS service + +Use the resource `cloudavenue_org_certificate_library` to reference the certificate used for the HTTPS service. +This example exposes a HTTPS service on the virtual IP `192.168.0.1` on port `443`. + +```hcl +resource "cloudavenue_elb_virtual_service" "example_https" { + name = "example" + enabled = true + + edge_gateway_id = cloudavenue_edgegateway.example.id + pool_id = cloudavenue_elb_pool.example.id + certificate_id = cloudavenue_org_certificate_library.example.id + + virtual_ip = "192.168.0.1" + + service_type = "HTTPS" // Use HTTPS or L4_TLS + service_ports = [ + { + start = 443 + } + ] +} +``` + +### Expose L4 service + +This example exposes a L4 TCP service on the virtual IP `192.168.0.1` on port `443`. + +```hcl +resource "cloudavenue_elb_virtual_service" "example_https" { + name = "example" + enabled = true + + edge_gateway_id = cloudavenue_edgegateway.example.id + pool_id = cloudavenue_elb_pool.example.id + + virtual_ip = "192.168.0.1" + + service_type = "L4_TCP" // Use L4_TCP or L4_UDP + service_ports = [ + { + start = 443 + } + ] +} +``` + +### Expose service with public IP + +The ELB virtual service require a **dedicated public IP** to expose the service. +If another resource use the same public IP, the ELB virtual service will fail to create. + +```hcl +resource "cloudavenue_elb_virtual_service" "example_https" { + name = "example" + enabled = true + + edge_gateway_id = cloudavenue_edgegateway.example.id + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = cloudavenue_public_ip.example.public_ip + + [...] +} +``` + +## Import + +Import is supported using the following syntax: +```shell +terraform import cloudavenue_elb_virtual_service.example edgeGatewayNameOrID.virtualServiceIDOrName +``` \ No newline at end of file diff --git a/docs/resources/org_certificate_library.md b/docs/resources/org_certificate_library.md new file mode 100644 index 00000000..73cb7427 --- /dev/null +++ b/docs/resources/org_certificate_library.md @@ -0,0 +1,46 @@ +--- +page_title: "cloudavenue_org_certificate_library Resource - cloudavenue" +subcategory: "Organization" +description: |- + The cloudavenue_org_certificate_library resource allows you to manage certificate in your organization's library. +--- + +# cloudavenue_org_certificate_library (Resource) + +The `cloudavenue_org_certificate_library` resource allows you to manage certificate in your organization's library. + +## Example Usage + +```terraform +resource "cloudavenue_org_certificate_library" "example" { + name = "company-certificate" + description = "dev environment certificate" + certificate = chomp(file("/my/certificate/path/cert.pem")) + private_key = chomp(file("/my/certificate/path/key.pem")) +} +``` + + +## Schema + +### Required + +- `certificate` (String) (ForceNew) The certificate content. It can be a PEM encoded certificate or a certificate chain. Contains all strings including the BEGIN CERTIFICATE and END CERTIFICATE lines. No empty lines are allowed. +- `name` (String) The name of the certificate library. + +### Optional + +- `description` (String) The description of the certificate library. +- `passphrase` (String, Sensitive) (ForceNew) The passphrase of the private key. +- `private_key` (String, Sensitive) (ForceNew) The private key of the certificate in PEM format. + +### Read-Only + +- `id` (String) The ID of the certificate library. + +## Import + +Import is supported using the following syntax: +```shell +terraform import cloudavenue_org_certificate_library.example nameOrID +``` \ No newline at end of file diff --git a/examples/data-sources/cloudavenue_alb_pool/data-source.tf b/examples/data-sources/cloudavenue_alb_pool/data-source.tf deleted file mode 100644 index b8ea64bf..00000000 --- a/examples/data-sources/cloudavenue_alb_pool/data-source.tf +++ /dev/null @@ -1,4 +0,0 @@ -data "cloudavenue_alb_pool" "example" { - edge_gateway_name = "MyEdgeGatewayName" - name = "MyAlbPoolName" -} diff --git a/examples/data-sources/cloudavenue_elb_pool/data-source.tf b/examples/data-sources/cloudavenue_elb_pool/data-source.tf new file mode 100644 index 00000000..38cbce11 --- /dev/null +++ b/examples/data-sources/cloudavenue_elb_pool/data-source.tf @@ -0,0 +1,4 @@ +data "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = cloudavenue_edge_gateway.example.id +} diff --git a/examples/data-sources/cloudavenue_elb_service_engine_group/data-source.tf b/examples/data-sources/cloudavenue_elb_service_engine_group/data-source.tf new file mode 100644 index 00000000..741f1001 --- /dev/null +++ b/examples/data-sources/cloudavenue_elb_service_engine_group/data-source.tf @@ -0,0 +1,8 @@ +data "cloudavenue_elb_service_engine_group" "example" { + name = "my-service-engine" + edge_gateway_name = data.cloudavenue_edge_gateway.example.name +} + +output "example" { + value = data.cloudavenue_elb_service_engine_group.example +} diff --git a/examples/data-sources/cloudavenue_elb_service_engine_groups/data-source.tf b/examples/data-sources/cloudavenue_elb_service_engine_groups/data-source.tf new file mode 100644 index 00000000..7e9bdea4 --- /dev/null +++ b/examples/data-sources/cloudavenue_elb_service_engine_groups/data-source.tf @@ -0,0 +1,7 @@ +data "cloudavenue_elb_service_engine_groups" "example" { + edge_gateway_name = data.cloudavenue_edge_gateway.example.name +} + +output "example" { + value = data.cloudavenue_elb_service_engine_groups.example +} diff --git a/examples/data-sources/cloudavenue_elb_virtual_service/data-source.tf b/examples/data-sources/cloudavenue_elb_virtual_service/data-source.tf new file mode 100644 index 00000000..a86d0880 --- /dev/null +++ b/examples/data-sources/cloudavenue_elb_virtual_service/data-source.tf @@ -0,0 +1,4 @@ +data "cloudavenue_elb_virtual_service" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id +} diff --git a/examples/data-sources/cloudavenue_org_certificate_library/data-source.tf b/examples/data-sources/cloudavenue_org_certificate_library/data-source.tf new file mode 100644 index 00000000..34643547 --- /dev/null +++ b/examples/data-sources/cloudavenue_org_certificate_library/data-source.tf @@ -0,0 +1,3 @@ +data "cloudavenue_org_certificate_library" "example" { + name = "my-certificate-library" +} diff --git a/examples/resources/cloudavenue_alb_pool/import.sh b/examples/resources/cloudavenue_alb_pool/import.sh deleted file mode 100644 index 35fd627f..00000000 --- a/examples/resources/cloudavenue_alb_pool/import.sh +++ /dev/null @@ -1,2 +0,0 @@ -# use the edge_gateway_name.alb_pool_name to import the ALB Pool -terraform import cloudavenue_alb_pool.test edge_gateway_name.alb_pool_name \ No newline at end of file diff --git a/examples/resources/cloudavenue_alb_pool/resource.tf b/examples/resources/cloudavenue_alb_pool/resource.tf deleted file mode 100644 index 3b38630a..00000000 --- a/examples/resources/cloudavenue_alb_pool/resource.tf +++ /dev/null @@ -1,48 +0,0 @@ -data "cloudavenue_tier0_vrfs" "example" {} - -resource "cloudavenue_edgegateway" "example" { - owner_name = "MyVDC" - tier0_vrf_name = data.cloudavenue_tier0_vrfs.example.names.0 - owner_type = "vdc" - lb_enabled = true -} - -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = cloudavenue_edgegateway.example.name - name = "Example" - - persistence_profile = { - type = "CLIENT_IP" - } - - members = [ - { - ip_address = "192.168.1.1" - port = "80" - }, - { - ip_address = "192.168.1.2" - port = "80" - }, - { - ip_address = "192.168.1.3" - port = "80" - } - ] - - health_monitors = ["UDP", "TCP"] -} - -data "cloudavenue_tier0_vrfs" "example" {} - -resource "cloudavenue_edgegateway" "example" { - owner_name = "MyVDC" - tier0_vrf_name = data.cloudavenue_tier0_vrfs.example.names.0 - owner_type = "vdc" - lb_enabled = true -} - -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = cloudavenue_edgegateway.example.name - name = "Example" -} \ No newline at end of file diff --git a/examples/resources/cloudavenue_elb_pool/import.sh b/examples/resources/cloudavenue_elb_pool/import.sh new file mode 100644 index 00000000..2ad303e0 --- /dev/null +++ b/examples/resources/cloudavenue_elb_pool/import.sh @@ -0,0 +1 @@ +terraform import cloudavenue_elb_pool.example edgeGatewayNameOrID.poolNameOrID \ No newline at end of file diff --git a/examples/resources/cloudavenue_elb_virtual_service/import.sh b/examples/resources/cloudavenue_elb_virtual_service/import.sh new file mode 100644 index 00000000..0e24b5e8 --- /dev/null +++ b/examples/resources/cloudavenue_elb_virtual_service/import.sh @@ -0,0 +1 @@ +terraform import cloudavenue_elb_virtual_service.example edgeGatewayNameOrID.virtualServiceIDOrName \ No newline at end of file diff --git a/examples/resources/cloudavenue_elb_virtual_service/resource.tf b/examples/resources/cloudavenue_elb_virtual_service/resource.tf new file mode 100644 index 00000000..2ced8682 --- /dev/null +++ b/examples/resources/cloudavenue_elb_virtual_service/resource.tf @@ -0,0 +1,16 @@ +resource "cloudavenue_elb_virtual_service" "example" { + name = "example" + enabled = true + + virtual_ip = "192.168.0.1" + + pool_id = cloudavenue_elb_pool.example.id + edge_gateway_id = cloudavenue_edgegateway.example.id + + service_type = "HTTP" + service_ports = [ + { + start = 80 + } + ] +} diff --git a/examples/resources/cloudavenue_org_certificate_library/import.sh b/examples/resources/cloudavenue_org_certificate_library/import.sh new file mode 100644 index 00000000..2d31d129 --- /dev/null +++ b/examples/resources/cloudavenue_org_certificate_library/import.sh @@ -0,0 +1 @@ +terraform import cloudavenue_org_certificate_library.example nameOrID \ No newline at end of file diff --git a/examples/resources/cloudavenue_org_certificate_library/resource.tf b/examples/resources/cloudavenue_org_certificate_library/resource.tf new file mode 100644 index 00000000..2ee74b95 --- /dev/null +++ b/examples/resources/cloudavenue_org_certificate_library/resource.tf @@ -0,0 +1,6 @@ +resource "cloudavenue_org_certificate_library" "example" { + name = "company-certificate" + description = "dev environment certificate" + certificate = chomp(file("/my/certificate/path/cert.pem")) + private_key = chomp(file("/my/certificate/path/key.pem")) +} diff --git a/go.mod b/go.mod index ff802647..4ef9ca08 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.23.3 require ( github.com/FrangipaneTeam/terraform-analytic-tool v0.0.12 github.com/aws/aws-sdk-go v1.55.6 + github.com/cilium/fake v0.7.0 github.com/drhodes/golorem v0.0.0-20220328165741-da82e5b29246 github.com/google/uuid v1.6.0 github.com/hashicorp/aws-sdk-go-base v1.1.0 @@ -19,7 +20,8 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0 github.com/iancoleman/strcase v0.3.0 - github.com/orange-cloudavenue/cloudavenue-sdk-go v0.21.3 + github.com/madflojo/testcerts v1.4.0 + github.com/orange-cloudavenue/cloudavenue-sdk-go v0.22.0 github.com/orange-cloudavenue/common-go/print v0.0.0-20250109171729-2be550d5d3ac github.com/orange-cloudavenue/common-go/utils v0.0.0-20240119163616-66b473d92339 github.com/orange-cloudavenue/terraform-plugin-framework-planmodifiers v1.4.0 @@ -127,7 +129,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/grpc v1.69.4 // indirect google.golang.org/protobuf v1.36.3 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 28a5e5db..66a542f0 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cilium/fake v0.7.0 h1:4EKBtTweQrJoD4q45qDGu8udulmYMo48Y0BhEbrB1jc= +github.com/cilium/fake v0.7.0/go.mod h1:hA1YsEjgIs5Gdeq/DVrDWGuhLCoVok7THTvQaGDO5bc= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -212,7 +214,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -223,6 +224,8 @@ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4F github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4= +github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= @@ -264,8 +267,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/orange-cloudavenue/cloudavenue-sdk-go v0.21.3 h1:Vy5n8fyt+KrwkGpG8/zun9yVQCgW8+6bvXmhjiKaSuc= -github.com/orange-cloudavenue/cloudavenue-sdk-go v0.21.3/go.mod h1:qijxgnnyB2JBkHWslCPD45NhJk5UVrVrm//Vq6qQ1c8= +github.com/orange-cloudavenue/cloudavenue-sdk-go v0.22.0 h1:KIzFTWapa7hDWKLRxvzUR6MvMK3u5UAAOo9jgVDhcZo= +github.com/orange-cloudavenue/cloudavenue-sdk-go v0.22.0/go.mod h1:rkWG4tFD+iBZ+dQyn9Cc0d1FqUoUuXkOiXmW/U2dn0c= github.com/orange-cloudavenue/common-go/print v0.0.0-20250109171729-2be550d5d3ac h1:f1Fd70+PMDTK6FE4gHdNfoHSQHLn5pfJMTjZPzOWZtc= github.com/orange-cloudavenue/common-go/print v0.0.0-20250109171729-2be550d5d3ac/go.mod h1:IYtCusqpEGS0dhC6F8X9GHrrt1gp1zHaNhSKGYV59Xg= github.com/orange-cloudavenue/common-go/utils v0.0.0-20240119163616-66b473d92339 h1:DEKcWLGbEhu/I6kn9NAXhVCFrbPhR+Ef7oLmpLVnnPM= diff --git a/internal/helpers/testsacc/template.go b/internal/helpers/testsacc/template.go index cc0be111..9e5cf819 100644 --- a/internal/helpers/testsacc/template.go +++ b/internal/helpers/testsacc/template.go @@ -15,6 +15,7 @@ import ( "strings" "text/template" + "github.com/cilium/fake" lorem "github.com/drhodes/golorem" "github.com/thanhpk/randstr" ) @@ -33,7 +34,13 @@ var ( extraOpts = append(extraOpts, "") } - randomString := generatePrefix + generateRandomString(extraOpts[0]) + randomString := "" + + if withoutPrefix(extraOpts[0]) { + randomString = generateRandomString(extraOpts[0]) + } else { + randomString = generatePrefix + generateRandomString(extraOpts[0]) + } (*KeyValueStore)[buildKeyValueStore(resourceName, key)] = randomString return returnWithQuotes(randomString) }, @@ -98,6 +105,10 @@ func buildKeyValueStore(resourceName, key string) string { func generateRandomString(format string) string { // generate random string switch format { + case "private-ipv4": + return fake.IP(fake.WithIPCIDR("10.0.0.0/8")) + case "public-ipv4": + return fake.IP(fake.WithIPCIDR("62.161.18.0/24")) case "longString": return lorem.Sentence(1, 5) default: @@ -105,6 +116,16 @@ func generateRandomString(format string) string { } } +func withoutPrefix(format string) bool { + f := []string{"private-ipv4", "public-ipv4"} + for _, v := range f { + if v == format { + return true + } + } + return false +} + // returnWithQuotes returns the given string with quotes. func returnWithQuotes(s string) string { s = strings.Trim(s, "\n") diff --git a/internal/provider/alb/pool_common.go b/internal/provider/alb/pool_common.go deleted file mode 100644 index 5db69016..00000000 --- a/internal/provider/alb/pool_common.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -package alb - -import ( - "errors" - - "github.com/vmware/go-vcloud-director/v2/govcd" - govcdtypes "github.com/vmware/go-vcloud-director/v2/types/v56" - - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" -) - -var ErrPersistenceProfileIsEmpty = errors.New("persistence profile is empty") - -type albPool interface { - GetID() string - GetName() string - GetAlbPool() (*govcd.NsxtAlbPool, error) -} - -func processMembers(poolMembers []govcdtypes.NsxtAlbPoolMember) (members []member) { - for _, poolMember := range poolMembers { - members = append(members, member{ - Enabled: types.BoolValue(poolMember.Enabled), - IPAddress: types.StringValue(poolMember.IpAddress), - Port: types.Int64Value(int64(poolMember.Port)), - Ratio: types.Int64Value(int64(*poolMember.Ratio)), - }) - } - return -} - -func processHealthMonitors(poolHealthMonitors []govcdtypes.NsxtAlbPoolHealthMonitor) (healthMonitors []string) { - for _, poolHealthMonitor := range poolHealthMonitors { - healthMonitors = append(healthMonitors, poolHealthMonitor.Type) - } - - return -} - -func processPersistenceProfile(poolPersistenceProfile *govcdtypes.NsxtAlbPoolPersistenceProfile) persistenceProfile { - if poolPersistenceProfile == nil { - return persistenceProfile{} - } - - return persistenceProfile{ - Type: types.StringValue(poolPersistenceProfile.Type), - Value: utils.StringValueOrNull(poolPersistenceProfile.Value), - } -} diff --git a/internal/provider/alb/pool_datasource.go b/internal/provider/alb/pool_datasource.go deleted file mode 100644 index 8b327be8..00000000 --- a/internal/provider/alb/pool_datasource.go +++ /dev/null @@ -1,177 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -// Package alb provides a Terraform datasource. -package alb - -import ( - "context" - "fmt" - - "github.com/vmware/go-vcloud-director/v2/govcd" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/types" - - "github.com/hashicorp/terraform-plugin-framework/datasource" - - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/edgegw" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/org" -) - -var ( - _ datasource.DataSource = &albPoolDataSource{} - _ datasource.DataSourceWithConfigure = &albPoolDataSource{} - _ albPool = &albPoolDataSource{} -) - -func NewAlbPoolDataSource() datasource.DataSource { - return &albPoolDataSource{} -} - -type albPoolDataSource struct { - client *client.CloudAvenue - org org.Org - edgegw edgegw.BaseEdgeGW - albPool base -} - -func (d *albPoolDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_pool" -} - -func (d *albPoolDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = albPoolSchema().GetDataSource(ctx) -} - -func (d *albPoolDataSource) Init(ctx context.Context, dm *albPoolModel) (diags diag.Diagnostics) { - d.albPool = base{ - name: dm.Name.ValueString(), - id: dm.ID.ValueString(), - } - - d.edgegw = edgegw.BaseEdgeGW{ - ID: dm.EdgeGatewayID, - Name: dm.EdgeGatewayName, - } - - d.org, diags = org.Init(d.client) - return -} - -func (d *albPoolDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*client.CloudAvenue) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", - fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - d.client = client -} - -func (d *albPoolDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - defer metrics.New("data.cloudavenue_alb_pool", d.client.GetOrgName(), metrics.Read)() - - var ( - data *albPoolModel - diags diag.Diagnostics - ) - // Read Terraform configuration data into the model - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(d.Init(ctx, data)...) - if resp.Diagnostics.HasError() { - return - } - - // Get albPool. - albPool, err := d.GetAlbPool() - if err != nil { - resp.Diagnostics.AddError("Unable to find ALB Pool", err.Error()) - return - } - - // Set data - data.ID = types.StringValue(albPool.NsxtAlbPool.ID) - data.Description = types.StringValue(albPool.NsxtAlbPool.Description) - data.Enabled = types.BoolValue(*albPool.NsxtAlbPool.Enabled) - data.Algorithm = types.StringValue(albPool.NsxtAlbPool.Algorithm) - data.DefaultPort = types.Int64Value(int64(*albPool.NsxtAlbPool.DefaultPort)) - data.GracefulTimeoutPeriod = types.Int64Value(int64(*albPool.NsxtAlbPool.GracefulTimeoutPeriod)) - data.PassiveMonitoringEnabled = types.BoolValue(*albPool.NsxtAlbPool.PassiveMonitoringEnabled) - - // Set members - members := processMembers(albPool.NsxtAlbPool.Members) - - data.Members, diags = types.SetValueFrom(ctx, types.ObjectType{AttrTypes: memberAttrTypes}, members) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Set health monitors. - healthMonitors := processHealthMonitors(albPool.NsxtAlbPool.HealthMonitors) - - data.HealthMonitors, diags = types.SetValueFrom(ctx, types.StringType, healthMonitors) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Set persistence profile - p := processPersistenceProfile(albPool.NsxtAlbPool.PersistenceProfile) - - data.PersistenceProfile, diags = types.ObjectValueFrom(ctx, persistenceProfileAttrTypes, p) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -// GetID returns the ID of the albPool. -func (d *albPoolDataSource) GetID() string { - return d.albPool.id -} - -// GetName returns the name of the albPool. -func (d *albPoolDataSource) GetName() string { - return d.albPool.name -} - -// GetAlbPool returns the govcd.NsxtAlbPool. -func (d *albPoolDataSource) GetAlbPool() (*govcd.NsxtAlbPool, error) { - if d.GetID() != "" { - return d.client.Vmware.GetAlbPoolById(d.GetID()) - } - - nsxtEdge, err := d.org.GetEdgeGateway(d.edgegw) - if err != nil { - return nil, fmt.Errorf("could not retrieve Edge gateway '%s'", d.edgegw.GetIDOrName()) - } - return d.client.Vmware.GetAlbPoolByName(nsxtEdge.GetID(), d.GetName()) -} diff --git a/internal/provider/alb/pool_resource.go b/internal/provider/alb/pool_resource.go deleted file mode 100644 index 6ed7c4e6..00000000 --- a/internal/provider/alb/pool_resource.go +++ /dev/null @@ -1,466 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -// Package alb provides a Terraform resource. -package alb - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/vmware/go-vcloud-director/v2/govcd" - govcdtypes "github.com/vmware/go-vcloud-director/v2/types/v56" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - - "github.com/hashicorp/terraform-plugin-framework/resource" - - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/edgegw" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/org" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" -) - -// Ensure the implementation satisfies the expected interfaces. -var ( - _ resource.Resource = &albPoolResource{} - _ resource.ResourceWithConfigure = &albPoolResource{} - _ resource.ResourceWithImportState = &albPoolResource{} - _ albPool = &albPoolResource{} -) - -// NewAlbPoolResource is a helper function to simplify the provider implementation. -func NewAlbPoolResource() resource.Resource { - return &albPoolResource{} -} - -// albPoolResource is the resource implementation. -type albPoolResource struct { - client *client.CloudAvenue - org org.Org - edgegw edgegw.BaseEdgeGW - albPool base -} - -// Metadata returns the resource type name. -func (r *albPoolResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_pool" -} - -// Schema defines the schema for the resource. -func (r *albPoolResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = albPoolSchema().GetResource(ctx) -} - -func (r *albPoolResource) Init(ctx context.Context, rm *albPoolModel) (diags diag.Diagnostics) { - r.albPool = base{ - name: rm.Name.ValueString(), - id: rm.ID.ValueString(), - } - - r.edgegw = edgegw.BaseEdgeGW{ - ID: rm.EdgeGatewayID, - Name: rm.EdgeGatewayName, - } - - r.org, diags = org.Init(r.client) - return -} - -func (r *albPoolResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*client.CloudAvenue) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - r.client = client -} - -// Create creates the resource and sets the initial Terraform state. -func (r *albPoolResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - defer metrics.New("cloudavenue_alb_pool", r.client.GetOrgName(), metrics.Create)() - - // Retrieve values from plan - var ( - plan *albPoolModel - ) - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - // Init - resp.Diagnostics.Append(r.Init(ctx, plan)...) - if resp.Diagnostics.HasError() { - return - } - - // Prepare config. - albPoolConfig, err := r.getAlbPoolConfig(ctx, plan) - if err != nil { - resp.Diagnostics.AddError("Unable to prepare ALB Pool Config", err.Error()) - return - } - - // Lock EdgeGW - edgeGW, err := r.org.GetEdgeGateway(r.edgegw) - if err != nil { - resp.Diagnostics.AddError("Unable to get Edge Gateway", err.Error()) - return - } - edgeGW.Lock(ctx) - defer edgeGW.Unlock(ctx) - - // Create ALB Pool - createdAlbPool, err := r.client.Vmware.CreateNsxtAlbPool(albPoolConfig) - if err != nil { - resp.Diagnostics.AddError("Unable to create ALB Pool", err.Error()) - return - } - - // Store ID - plan.ID = utils.StringValueOrNull(createdAlbPool.NsxtAlbPool.ID) - - // Set state to fully populated data - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -// Read refreshes the Terraform state with the latest data. -func (r *albPoolResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - defer metrics.New("cloudavenue_alb_pool", r.client.GetOrgName(), metrics.Read)() - - var ( - state *albPoolModel - diags diag.Diagnostics - ) - - // Get current state - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(r.Init(ctx, state)...) - if resp.Diagnostics.HasError() { - return - } - - // Get albPool. - albPool, err := r.GetAlbPool() - if err != nil { - if govcd.ContainsNotFound(err) { - resp.State.RemoveResource(ctx) - return - } - - resp.Diagnostics.AddError("Unable to find ALB Pool", err.Error()) - return - } - - // Set data - plan := &albPoolModel{ - ID: utils.StringValueOrNull(albPool.NsxtAlbPool.ID), - Name: state.Name, - Description: utils.StringValueOrNull(albPool.NsxtAlbPool.Description), - EdgeGatewayID: state.EdgeGatewayID, - EdgeGatewayName: state.EdgeGatewayName, - Enabled: types.BoolValue(*albPool.NsxtAlbPool.Enabled), - Algorithm: utils.StringValueOrNull(albPool.NsxtAlbPool.Algorithm), - DefaultPort: types.Int64Value(int64(*albPool.NsxtAlbPool.DefaultPort)), - GracefulTimeoutPeriod: types.Int64Value(int64(*albPool.NsxtAlbPool.GracefulTimeoutPeriod)), - PassiveMonitoringEnabled: types.BoolValue(*albPool.NsxtAlbPool.PassiveMonitoringEnabled), - Members: types.SetNull(types.ObjectType{AttrTypes: memberAttrTypes}), - HealthMonitors: types.SetNull(types.StringType), - PersistenceProfile: types.ObjectNull(persistenceProfileAttrTypes), - } - - // Set members - if members := processMembers(albPool.NsxtAlbPool.Members); len(members) > 0 { - plan.Members, diags = types.SetValueFrom(ctx, types.ObjectType{AttrTypes: memberAttrTypes}, members) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } - - // Set health monitors. - healthMonitors := processHealthMonitors(albPool.NsxtAlbPool.HealthMonitors) - - if len(healthMonitors) > 0 { - plan.HealthMonitors, diags = types.SetValueFrom(ctx, types.StringType, healthMonitors) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } - - // Set persistence profile - p := processPersistenceProfile(albPool.NsxtAlbPool.PersistenceProfile) - - if p != (persistenceProfile{}) { - plan.PersistenceProfile, diags = types.ObjectValueFrom(ctx, persistenceProfileAttrTypes, p) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } - - // Set refreshed state - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -// Update updates the resource and sets the updated Terraform state on success. -func (r *albPoolResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - defer metrics.New("cloudavenue_alb_pool", r.client.GetOrgName(), metrics.Update)() - - var plan *albPoolModel - - // Get current state - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - // Init - resp.Diagnostics.Append(r.Init(ctx, plan)...) - if resp.Diagnostics.HasError() { - return - } - - // Get albPool - albPool, err := r.GetAlbPool() - if err != nil { - resp.Diagnostics.AddError("Unable to find ALB Pool", err.Error()) - return - } - - // Prepare config. - albPoolConfig, err := r.getAlbPoolConfig(ctx, plan) - if err != nil { - resp.Diagnostics.AddError("Unable to prepare ALB Pool Config", err.Error()) - return - } - - // Lock EdgeGW - edgeGW, err := r.org.GetEdgeGateway(r.edgegw) - if err != nil { - resp.Diagnostics.AddError("Unable to get Edge Gateway", err.Error()) - return - } - edgeGW.Lock(ctx) - defer edgeGW.Unlock(ctx) - - // Update ALB Pool. - _, err = albPool.Update(albPoolConfig) - if err != nil { - resp.Diagnostics.AddError("Unable to update ALB Pool", err.Error()) - return - } - - // Set state to fully populated data - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -// Delete deletes the resource and removes the Terraform state on success. -func (r *albPoolResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - defer metrics.New("cloudavenue_alb_pool", r.client.GetOrgName(), metrics.Delete)() - - var state *albPoolModel - - // Get current state - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - // Init - resp.Diagnostics.Append(r.Init(ctx, state)...) - if resp.Diagnostics.HasError() { - return - } - - // Lock EdgeGW - edgeGW, err := r.org.GetEdgeGateway(r.edgegw) - if err != nil { - resp.Diagnostics.AddError("Unable to get Edge Gateway", err.Error()) - return - } - edgeGW.Lock(ctx) - defer edgeGW.Unlock(ctx) - - // Get albPool - albPool, err := r.GetAlbPool() - if err != nil { - resp.Diagnostics.AddError("Unable to find ALB Pool", err.Error()) - return - } - - err = albPool.Delete() - if err != nil { - resp.Diagnostics.AddError("Unable to delete ALB Pool", err.Error()) - return - } -} - -func (r *albPoolResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - defer metrics.New("cloudavenue_alb_pool", r.client.GetOrgName(), metrics.Import)() - - idParts := strings.Split(req.ID, ".") - - if len(idParts) != 2 { - resp.Diagnostics.AddError( - "Unexpected Import Identifier", - fmt.Sprintf("Expected import identifier with format: edge_gateway_name.alb_pool_name. Got: %q", req.ID), - ) - return - } - - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("edge_gateway_name"), idParts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[1])...) -} - -// GetID returns the ID of the albPool. -func (r *albPoolResource) GetID() string { - return r.albPool.id -} - -// GetName returns the name of the albPool. -func (r *albPoolResource) GetName() string { - return r.albPool.name -} - -// GetAlbPool returns the govcd.NsxtAlbPool. -func (r *albPoolResource) GetAlbPool() (*govcd.NsxtAlbPool, error) { - if r.GetID() != "" { - return r.client.Vmware.GetAlbPoolById(r.GetID()) - } - - nsxtEdge, err := r.org.GetEdgeGateway(r.edgegw) - if err != nil { - return nil, fmt.Errorf("could not retrieve Edge gateway '%s'", r.edgegw.GetIDOrName()) - } - return r.client.Vmware.GetAlbPoolByName(nsxtEdge.EdgeGateway.ID, r.GetName()) -} - -// getAlbPoolConfig is the main function for getting *govcdtypes.NsxtAlbPool for API request. It nests multiple smaller -// functions for smaller types. -func (r *albPoolResource) getAlbPoolConfig(ctx context.Context, d *albPoolModel) (*govcdtypes.NsxtAlbPool, error) { - edge, err := r.org.GetEdgeGateway(r.edgegw) - if err != nil { - return nil, err - } - - albPoolConfig := &govcdtypes.NsxtAlbPool{ - ID: r.GetID(), - Name: d.Name.ValueString(), - Description: d.Description.ValueString(), - Enabled: d.Enabled.ValueBoolPointer(), - GatewayRef: govcdtypes.OpenApiReference{ - ID: edge.GetID(), - }, - Algorithm: d.Algorithm.ValueString(), - DefaultPort: utils.TakeIntPointer(int(d.DefaultPort.ValueInt64())), - GracefulTimeoutPeriod: utils.TakeIntPointer(int(d.GracefulTimeoutPeriod.ValueInt64())), - PassiveMonitoringEnabled: d.PassiveMonitoringEnabled.ValueBoolPointer(), - } - - poolMembers, err := r.getAlbPoolMembersType(ctx, d) - if err != nil { - return nil, fmt.Errorf("error defining pool members: %w", err) - } - albPoolConfig.Members = poolMembers - - persistenceProfile, err := r.getAlbPoolPersistenceProfileType(ctx, d) - if err != nil && !errors.Is(err, ErrPersistenceProfileIsEmpty) { - return nil, fmt.Errorf("error defining persistence profile: %w", err) - } - albPoolConfig.PersistenceProfile = persistenceProfile - - healthMonitors, err := r.getAlbPoolHealthMonitorType(ctx, d) - if err != nil { - return nil, fmt.Errorf("error defining health monitors: %w", err) - } - albPoolConfig.HealthMonitors = healthMonitors - - return albPoolConfig, nil -} - -// getAlbPoolMembersType. -func (r *albPoolResource) getAlbPoolMembersType(ctx context.Context, d *albPoolModel) ([]govcdtypes.NsxtAlbPoolMember, error) { - var members []member - diags := d.Members.ElementsAs(ctx, &members, true) - if diags.HasError() { - return nil, errors.New(diags[0].Detail()) - } - memberSlice := make([]govcdtypes.NsxtAlbPoolMember, 0) - for _, memberDefinition := range members { - memberSlice = append(memberSlice, govcdtypes.NsxtAlbPoolMember{ - Enabled: memberDefinition.Enabled.ValueBool(), - IpAddress: memberDefinition.IPAddress.ValueString(), - Ratio: utils.TakeIntPointer(int(memberDefinition.Ratio.ValueInt64())), - Port: int(memberDefinition.Port.ValueInt64()), - }) - } - return memberSlice, nil -} - -// getAlbPoolPersistenceProfileType. -func (r *albPoolResource) getAlbPoolPersistenceProfileType(ctx context.Context, d *albPoolModel) (*govcdtypes.NsxtAlbPoolPersistenceProfile, error) { - if d.PersistenceProfile.IsNull() { - return nil, ErrPersistenceProfileIsEmpty - } - - p := &persistenceProfile{} - if diags := d.PersistenceProfile.As(ctx, p, basetypes.ObjectAsOptions{ - UnhandledNullAsEmpty: false, - UnhandledUnknownAsEmpty: false, - }); diags.HasError() { - return nil, errors.New(diags[0].Detail()) - } - - return &govcdtypes.NsxtAlbPoolPersistenceProfile{ - Type: p.Type.ValueString(), - Value: p.Value.ValueString(), - }, nil -} - -// getAlbPoolHealthMonitorType. -func (r *albPoolResource) getAlbPoolHealthMonitorType(ctx context.Context, d *albPoolModel) (healthMonitors []govcdtypes.NsxtAlbPoolHealthMonitor, err error) { - var healthMonitorsSlice []string - - if diags := d.HealthMonitors.ElementsAs(ctx, &healthMonitorsSlice, true); diags.HasError() { - return nil, errors.New(diags[0].Detail()) - } - - for _, healthMonitor := range healthMonitorsSlice { - healthMonitors = append(healthMonitors, govcdtypes.NsxtAlbPoolHealthMonitor{ - Type: healthMonitor, - }) - } - - return -} diff --git a/internal/provider/alb/pool_schema.go b/internal/provider/alb/pool_schema.go deleted file mode 100644 index eb309c4c..00000000 --- a/internal/provider/alb/pool_schema.go +++ /dev/null @@ -1,291 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -package alb - -import ( - superschema "github.com/orange-cloudavenue/terraform-plugin-framework-superschema" - fstringvalidator "github.com/orange-cloudavenue/terraform-plugin-framework-validators/stringvalidator" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - - schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - schemaR "github.com/hashicorp/terraform-plugin-framework/resource/schema" - - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - - "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" -) - -/* -albPoolSchema - -This function is used to create the schema for the ALB Pool resource and datasource. -*/ -func albPoolSchema() superschema.Schema { - return superschema.Schema{ - Resource: superschema.SchemaDetails{ - MarkdownDescription: "Provides a resource to manage Advanced Load Balancer Pools. Pools maintain the list of assigned servers and perform health monitoring, load balancing, and persistence. A pool may be used or referenced by only one virtual service at a time.", - }, - DataSource: superschema.SchemaDetails{ - MarkdownDescription: "Provides a data source to manage Advanced Load Balancer Pools. Pools maintain the list of assigned servers and perform health monitoring, load balancing, and persistence.", - }, - Attributes: map[string]superschema.Attribute{ - "id": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "ID of ALB Pool.", - Computed: true, - }, - Resource: &schemaR.StringAttribute{ - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - "name": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Name of ALB Pool.", - Required: true, - }, - }, - "edge_gateway_id": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Edge gateway ID in which ALB Pool", - Optional: true, - Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_id"), path.MatchRoot("edge_gateway_name")), - }, - }, - Resource: &schemaR.StringAttribute{ - MarkdownDescription: "should be created.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - DataSource: &schemaD.StringAttribute{ - MarkdownDescription: "was created.", - }, - }, - "edge_gateway_name": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Edge gateway Name in which ALB Pool", - Optional: true, - Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_id"), path.MatchRoot("edge_gateway_name")), - }, - }, - Resource: &schemaR.StringAttribute{ - MarkdownDescription: "should be created.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - DataSource: &schemaD.StringAttribute{ - MarkdownDescription: "was created.", - }, - }, - "enabled": superschema.BoolAttribute{ - Common: &schemaR.BoolAttribute{ - MarkdownDescription: "Define if ALB Pool is enabled or not.", - Computed: true, - }, - Resource: &schemaR.BoolAttribute{ - Optional: true, - Default: booldefault.StaticBool(true), - }, - }, - "description": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Description of ALB Pool.", - }, - Resource: &schemaR.StringAttribute{ - Optional: true, - }, - DataSource: &schemaD.StringAttribute{ - Computed: true, - }, - }, - "algorithm": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Algorithm for selecting members within a pool.", - Computed: true, - }, - Resource: &schemaR.StringAttribute{ - Optional: true, - Default: stringdefault.StaticString("LEAST_CONNECTIONS"), - Validators: []validator.String{ - stringvalidator.OneOf("ROUND_ROBIN", "CONSISTENT_HASH", "LEAST_CONNECTIONS"), - }, - }, - }, - "default_port": superschema.Int64Attribute{ - Common: &schemaR.Int64Attribute{ - MarkdownDescription: "Destination server port used for traffic sent to a member.", - Computed: true, - }, - Resource: &schemaR.Int64Attribute{ - Optional: true, - Default: int64default.StaticInt64(80), - Validators: []validator.Int64{ - int64validator.Between(1, 65535), - }, - }, - }, - "graceful_timeout_period": superschema.Int64Attribute{ - Common: &schemaR.Int64Attribute{ - MarkdownDescription: "Maximum time in minutes allowed for gracefully disabling a pool member.", - Computed: true, - }, - Resource: &schemaR.Int64Attribute{ - Optional: true, - Default: int64default.StaticInt64(1), - }, - }, - "members": superschema.SetNestedAttribute{ - Common: &schemaR.SetNestedAttribute{ - MarkdownDescription: "ALB Pool Member(s).", - }, - Resource: &schemaR.SetNestedAttribute{ - Optional: true, - }, - DataSource: &schemaD.SetNestedAttribute{ - Computed: true, - }, - Attributes: map[string]superschema.Attribute{ - "enabled": superschema.BoolAttribute{ - Common: &schemaR.BoolAttribute{ - MarkdownDescription: "Indicates whether a pool member accepts traffic.", - Computed: true, - }, - Resource: &schemaR.BoolAttribute{ - Optional: true, - Default: booldefault.StaticBool(true), - }, - }, - "ip_address": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "IP address of a pool member.", - }, - Resource: &schemaR.StringAttribute{ - Required: true, - Validators: []validator.String{ - fstringvalidator.IsIP(), - }, - }, - DataSource: &schemaD.StringAttribute{ - Computed: true, - }, - }, - "port": superschema.Int64Attribute{ - Common: &schemaR.Int64Attribute{ - MarkdownDescription: "Member port.", - }, - Resource: &schemaR.Int64Attribute{ - Required: true, - Validators: []validator.Int64{ - int64validator.Between(1, 65535), - }, - }, - DataSource: &schemaD.Int64Attribute{ - Computed: true, - }, - }, - "ratio": superschema.Int64Attribute{ - Common: &schemaR.Int64Attribute{ - MarkdownDescription: "Ratio of selecting eligible servers in the pool.", - Computed: true, - }, - Resource: &schemaR.Int64Attribute{ - Optional: true, - Default: int64default.StaticInt64(1), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - }, - }, - }, - "health_monitors": superschema.SetAttribute{ - Common: &schemaR.SetAttribute{ - MarkdownDescription: "List of health monitors type to activate.", - ElementType: types.StringType, - }, - Resource: &schemaR.SetAttribute{ - Optional: true, - Validators: []validator.Set{ - setvalidator.ValueStringsAre(stringvalidator.OneOf("HTTP", "HTTPS", "TCP", "UDP", "PING")), - }, - }, - DataSource: &schemaD.SetAttribute{ - Computed: true, - }, - }, - "persistence_profile": superschema.SingleNestedAttribute{ - Common: &schemaR.SingleNestedAttribute{ - MarkdownDescription: "Persistence profile ensures that a user remains connected to the same server for a specified duration. If the persistence profile is unmanaged by Cloud Avenue, updates with unchanged values will continue using the same unmanaged profile. However, any changes to the persistence profile will prompt Cloud Avenue to switch the pool to a profile it manages.", - }, - Resource: &schemaR.SingleNestedAttribute{ - Optional: true, - }, - DataSource: &schemaD.SingleNestedAttribute{ - Computed: true, - }, - Attributes: map[string]superschema.Attribute{ - "type": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Type of persistence strategy.", - }, - Resource: &schemaR.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf("CLIENT_IP", "HTTP_COOKIE"), - }, - }, - DataSource: &schemaD.StringAttribute{ - Computed: true, - }, - }, - "value": superschema.StringAttribute{ - Common: &schemaR.StringAttribute{ - MarkdownDescription: "Value of attribute based on persistence type.", - }, - Resource: &schemaR.StringAttribute{ - Optional: true, - Validators: []validator.String{ - fstringvalidator.RequireIfAttributeIsOneOf(path.MatchRoot("persistence_profile").AtName("type"), []attr.Value{types.StringValue("HTTP_COOKIE")}), - }, - }, - DataSource: &schemaD.StringAttribute{ - Computed: true, - }, - }, - }, - }, - "passive_monitoring_enabled": superschema.BoolAttribute{ - Common: &schemaR.BoolAttribute{ - MarkdownDescription: "Monitors if the traffic is accepted by node.", - Computed: true, - }, - Resource: &schemaR.BoolAttribute{ - Optional: true, - Default: booldefault.StaticBool(true), - }, - }, - }, - } -} diff --git a/internal/provider/alb/pool_types.go b/internal/provider/alb/pool_types.go deleted file mode 100644 index 082f4332..00000000 --- a/internal/provider/alb/pool_types.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -package alb - -import ( - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -type albPoolModel struct { - ID types.String `tfsdk:"id"` - EdgeGatewayID types.String `tfsdk:"edge_gateway_id"` - EdgeGatewayName types.String `tfsdk:"edge_gateway_name"` - Name types.String `tfsdk:"name"` - Enabled types.Bool `tfsdk:"enabled"` - Description types.String `tfsdk:"description"` - Algorithm types.String `tfsdk:"algorithm"` - DefaultPort types.Int64 `tfsdk:"default_port"` - GracefulTimeoutPeriod types.Int64 `tfsdk:"graceful_timeout_period"` - Members types.Set `tfsdk:"members"` - HealthMonitors types.Set `tfsdk:"health_monitors"` - PersistenceProfile types.Object `tfsdk:"persistence_profile"` - PassiveMonitoringEnabled types.Bool `tfsdk:"passive_monitoring_enabled"` - - // CACertificateIDs types.Set `tfsdk:"ca_certificate_ids"` - // CNCheckEnabled types.Bool `tfsdk:"cn_check_enabled"` - // DomainNames types.Set `tfsdk:"domain_names"` -} - -type member struct { - Enabled types.Bool `tfsdk:"enabled"` - IPAddress types.String `tfsdk:"ip_address"` - Port types.Int64 `tfsdk:"port"` - Ratio types.Int64 `tfsdk:"ratio"` -} - -var memberAttrTypes = map[string]attr.Type{ - "enabled": types.BoolType, - "ip_address": types.StringType, - "port": types.Int64Type, - "ratio": types.Int64Type, -} - -type persistenceProfile struct { - Type types.String `tfsdk:"type"` - Value types.String `tfsdk:"value"` -} - -var persistenceProfileAttrTypes = map[string]attr.Type{ - "type": types.StringType, - "value": types.StringType, -} diff --git a/internal/provider/alb/base.go b/internal/provider/elb/base.go similarity index 79% rename from internal/provider/alb/base.go rename to internal/provider/elb/base.go index f0f2cd52..bd60b4dd 100644 --- a/internal/provider/alb/base.go +++ b/internal/provider/elb/base.go @@ -7,13 +7,8 @@ * or see the "LICENSE" file for more details. */ -package alb +package elb const ( - categoryName = "alb" + categoryName = "elb" ) - -type base struct { - id string - name string -} diff --git a/internal/provider/elb/pool_datasource.go b/internal/provider/elb/pool_datasource.go new file mode 100644 index 00000000..5b21be76 --- /dev/null +++ b/internal/provider/elb/pool_datasource.go @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +// Package elb provides a Terraform datasource. +package elb + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +var ( + _ datasource.DataSource = &PoolDataSource{} + _ datasource.DataSourceWithConfigure = &PoolDataSource{} +) + +func NewPoolDataSource() datasource.DataSource { + return &PoolDataSource{} +} + +type PoolDataSource struct { + client *client.CloudAvenue + elb edgeloadbalancer.Client +} + +// Init Initializes the data source. +func (d *PoolDataSource) Init(ctx context.Context, dm *PoolModel) (diags diag.Diagnostics) { + var err error + + d.elb, err = edgeloadbalancer.NewClient() + if err != nil { + diags.AddError("Error creating elb client", err.Error()) + } + + return +} + +func (d *PoolDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_pool" +} + +func (d *PoolDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = poolSchema(ctx).GetDataSource(ctx) +} + +func (d *PoolDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *PoolDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_elb_pool", d.client.GetOrgName(), metrics.Read)() + + config := &PoolModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + s := &PoolResource{ + client: d.client, + elb: d.elb, + } + + // Read data from the API + data, found, diags := s.read(ctx, config) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found") + return + } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/elb/pool_resource.go b/internal/provider/elb/pool_resource.go new file mode 100644 index 00000000..716b08f2 --- /dev/null +++ b/internal/provider/elb/pool_resource.go @@ -0,0 +1,489 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + "fmt" + "strings" + + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + v1 "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/mutex" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &PoolResource{} + _ resource.ResourceWithConfigure = &PoolResource{} + _ resource.ResourceWithImportState = &PoolResource{} +) + +// NewPoolResource is a helper function to simplify the provider implementation. +func NewPoolResource() resource.Resource { + return &PoolResource{} +} + +// PoolResource is the resource implementation. +type PoolResource struct { + client *client.CloudAvenue + elb edgeloadbalancer.Client + edge *v1.EdgeClient +} + +// Init Initializes the resource. +func (r *PoolResource) Init(ctx context.Context, rm *PoolModel) (diags diag.Diagnostics) { + var err error + + r.elb, err = edgeloadbalancer.NewClient() + if err != nil { + diags.AddError("Error creating elb client", err.Error()) + } + + eIDOrName := rm.EdgeGatewayID.Get() + if eIDOrName == "" { + eIDOrName = rm.EdgeGatewayName.Get() + } + r.edge, err = r.client.CAVSDK.V1.EdgeGateway.Get(eIDOrName) + if err != nil { + diags.AddError("Error creating edge client", err.Error()) + } + + rm.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, r.edge.GetID()).String()) + rm.EdgeGatewayName.Set(r.edge.GetName()) + + return +} + +// Metadata returns the resource type name. +func (r *PoolResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_pool" +} + +// Schema defines the schema for the resource. +func (r *PoolResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = poolSchema(ctx).GetResource(ctx) +} + +func (r *PoolResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +// Create creates the resource and sets the initial Terraform state. +func (r *PoolResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer metrics.New("cloudavenue_elb_pool", r.client.GetOrgName(), metrics.Create)() + + plan := &PoolModel{} + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource creation logic here. + */ + + mutex.GlobalMutex.KvLock(ctx, plan.EdgeGatewayID.Get()) + defer mutex.GlobalMutex.KvUnlock(ctx, plan.EdgeGatewayID.Get()) + + model, d := plan.ToSDKPoolModelRequest(ctx, r.client) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + poolCreated, err := r.elb.CreatePool(ctx, *model) + if err != nil { + resp.Diagnostics.AddError("Error creating pool", err.Error()) + return + } + + plan.ID.Set(poolCreated.ID) + + // Use generic read function to refresh the state + state, found, d := r.read(ctx, plan) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found after creation") + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *PoolResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer metrics.New("cloudavenue_elb_pool", r.client.GetOrgName(), metrics.Read)() + + state := &PoolModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Refresh the state + stateRefreshed, found, d := r.read(ctx, state) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found after refresh") + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *PoolResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer metrics.New("cloudavenue_elb_pool", r.client.GetOrgName(), metrics.Update)() + + var ( + plan = &PoolModel{} + state = &PoolModel{} + ) + + // Get current plan and state + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource update here + */ + + mutex.GlobalMutex.KvLock(ctx, plan.EdgeGatewayID.Get()) + defer mutex.GlobalMutex.KvUnlock(ctx, plan.EdgeGatewayID.Get()) + + model, d := plan.ToSDKPoolModelRequest(ctx, r.client) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + _, err := r.elb.UpdatePool(ctx, state.ID.Get(), *model) + if err != nil { + resp.Diagnostics.AddError("Error updating pool", err.Error()) + return + } + + // Use generic read function to refresh the state + stateRefreshed, found, d := r.read(ctx, plan) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found after update") + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *PoolResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer metrics.New("cloudavenue_elb_pool", r.client.GetOrgName(), metrics.Delete)() + + state := &PoolModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource deletion here + */ + + mutex.GlobalMutex.KvLock(ctx, state.EdgeGatewayID.Get()) + defer mutex.GlobalMutex.KvUnlock(ctx, state.EdgeGatewayID.Get()) + + if err := r.elb.DeletePool(ctx, state.ID.Get()); err != nil { + resp.Diagnostics.AddError("Error deleting pool", err.Error()) + return + } +} + +func (r *PoolResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer metrics.New("cloudavenue_elb_pool", r.client.GetOrgName(), metrics.Import)() + + // Import format is edgeGatewayIDOrName.poolIDOrName + + // * Import with custom logic + idParts := strings.Split(req.ID, ".") + + if len(idParts) != 2 { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: edgeGatewayIDOrName.poolIDOrName. Got: %q", req.ID), + ) + return + } + + x := &PoolModel{ + ID: supertypes.NewStringNull(), + Name: supertypes.NewStringNull(), + EdgeGatewayID: supertypes.NewStringNull(), + EdgeGatewayName: supertypes.NewStringNull(), + } + + if urn.IsEdgeGateway(idParts[0]) { + x.EdgeGatewayID.Set(idParts[0]) + } else { + edge, err := r.client.CAVSDK.V1.EdgeGateway.Get(idParts[0]) + if err != nil { + resp.Diagnostics.AddError("Error retrieving Edge Gateway", err.Error()) + return + } + x.EdgeGatewayName.Set(idParts[0]) + x.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, edge.GetID()).String()) + } + + if urn.IsLoadBalancerPool(idParts[1]) { + x.ID.Set(idParts[1]) + } else { + x.Name.Set(idParts[1]) + } + + resp.Diagnostics.Append(r.Init(ctx, x)...) + if resp.Diagnostics.HasError() { + return + } + + stateRefreshed, found, d := r.read(ctx, x) + if !found { + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// * CustomFuncs + +// read is a generic read function that can be used by the resource Create, Read and Update functions. +func (r *PoolResource) read(ctx context.Context, planOrState *PoolModel) (stateRefreshed *PoolModel, found bool, diags diag.Diagnostics) { + stateRefreshed = planOrState.Copy() + + /* + Implement the resource read here + */ + + idOrName := planOrState.ID.Get() + if idOrName == "" { + idOrName = planOrState.Name.Get() + } + + data, err := r.elb.GetPool(ctx, planOrState.EdgeGatewayID.Get(), idOrName) + if err != nil { + if govcd.ContainsNotFound(err) { + return nil, false, nil + } + diags.AddError("Error retrieving pool", err.Error()) + return nil, true, diags + } + + stateRefreshed.ID.Set(data.ID) + stateRefreshed.Name.Set(data.Name) + stateRefreshed.Description.Set(data.Description) + stateRefreshed.EdgeGatewayID.Set(data.GatewayRef.ID) + stateRefreshed.EdgeGatewayName.Set(data.GatewayRef.Name) + stateRefreshed.Enabled.SetPtr(data.Enabled) + stateRefreshed.Algorithm.Set(string(data.Algorithm)) + stateRefreshed.DefaultPort.SetIntPtr(data.DefaultPort) + + // * Members + members := &PoolModelMembers{ + GracefulTimeoutPeriod: supertypes.NewStringNull(), + TargetGroup: supertypes.NewStringNull(), + Targets: supertypes.NewListNestedObjectValueOfNull[PoolModelMembersIPAddress](ctx), + } + + if data.MemberGroupRef != nil { + members.TargetGroup.Set(data.MemberGroupRef.ID) + } + if data.GracefulTimeoutPeriod != nil { + members.GracefulTimeoutPeriod.Set(fmt.Sprintf("%d", *data.GracefulTimeoutPeriod)) + } + + if len(data.Members) != 0 { + ipAddress := make([]*PoolModelMembersIPAddress, 0) + for _, m := range data.Members { + ipa := &PoolModelMembersIPAddress{ + Enabled: supertypes.NewBoolNull(), + IPAddress: supertypes.NewStringNull(), + Port: supertypes.NewInt64Null(), + Ratio: supertypes.NewInt64Null(), + } + + ipa.Enabled.Set(m.Enabled) + ipa.IPAddress.Set(m.IPAddress) + ipa.Port.SetInt(m.Port) + ipa.Ratio.SetIntPtr(m.Ratio) + + ipAddress = append(ipAddress, ipa) + } + + diags.Append(members.Targets.Set(ctx, ipAddress)...) + if diags.HasError() { + return nil, true, diags + } + } + + diags.Append(stateRefreshed.Members.Set(ctx, members)...) + if diags.HasError() { + return nil, true, diags + } + + // * Health + health := &PoolModelHealth{ + PassiveMonitoringEnabled: supertypes.NewBoolNull(), + Monitors: supertypes.NewListValueOfNull[string](ctx), + } + + health.PassiveMonitoringEnabled.SetPtr(data.PassiveMonitoringEnabled) + + // prevent unexpected new value: .health.monitors: was null, but now cty.ListValEmpty(cty.String). + if len(data.HealthMonitors) != 0 { + monitors := []string{} + for _, m := range data.HealthMonitors { + monitors = append(monitors, string(m.Type)) + } + + diags.Append(health.Monitors.Set(ctx, monitors)...) + if diags.HasError() { + return nil, true, diags + } + } + + diags.Append(stateRefreshed.Health.Set(ctx, health)...) + if diags.HasError() { + return nil, true, diags + } + + // * TLS + tls := &PoolModelTLS{ + Enabled: supertypes.NewBoolNull(), + DomainNames: supertypes.NewListValueOfNull[string](ctx), + CaCertificateRefs: supertypes.NewListValueOfNull[string](ctx), + CommonNameCheckEnabled: supertypes.NewBoolNull(), + } + + tls.Enabled.SetPtr(data.SSLEnabled) + tls.CommonNameCheckEnabled.SetPtr(data.CommonNameCheckEnabled) + diags.Append(tls.DomainNames.Set(ctx, data.DomainNames)...) + if diags.HasError() { + return nil, true, diags + } + + // prevent unexpected new value: .tls.ca_certificate_refs: was null, but now cty.ListValEmpty(cty.String). + if len(data.CaCertificateRefs) != 0 { + refs := []string{} + for _, ca := range data.CaCertificateRefs { + refs = append(refs, ca.ID) + } + + diags.Append(tls.CaCertificateRefs.Set(ctx, refs)...) + if diags.HasError() { + return nil, true, diags + } + } + + diags.Append(stateRefreshed.TLS.Set(ctx, tls)...) + if diags.HasError() { + return nil, true, diags + } + + // * Persistence + persistence := &PoolModelPersistence{ + Type: supertypes.NewStringNull(), + Value: supertypes.NewStringNull(), + } + + if data.PersistenceProfile != nil { + persistence.Type.Set(string(data.PersistenceProfile.Type)) + persistence.Value.Set(data.PersistenceProfile.Value) + } + diags.Append(stateRefreshed.Persistence.Set(ctx, persistence)...) + if diags.HasError() { + return nil, true, diags + } + + return stateRefreshed, true, nil +} diff --git a/internal/provider/elb/pool_schema.go b/internal/provider/elb/pool_schema.go new file mode 100644 index 00000000..48fd3885 --- /dev/null +++ b/internal/provider/elb/pool_schema.go @@ -0,0 +1,441 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + "slices" + + superschema "github.com/orange-cloudavenue/terraform-plugin-framework-superschema" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + fstringvalidator "github.com/orange-cloudavenue/terraform-plugin-framework-validators/stringvalidator" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + schemaR "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" +) + +func poolSchema(ctx context.Context) superschema.Schema { + return superschema.Schema{ + Resource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_elb_pool` resource allows you to manage edgegateway load balancer pools. Pools maintain the list of servers assigned to them and perform health monitoring, load balancing, persistence. A pool may only be used or referenced by only one virtual service at a time.", + }, + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_elb_pool` data source allows you to retrieve information about an existing edgegateway load balancer pool.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the pool.", + }, + Resource: &schemaR.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + "name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the pool.", + Required: true, + }, + }, + "edge_gateway_name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the Edge Gateway.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_name"), path.MatchRoot("edge_gateway_id")), + }, + }, + }, + "edge_gateway_id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The ID of the Edge Gateway.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_name"), path.MatchRoot("edge_gateway_id")), + }, + }, + }, + "description": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the pool.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "enabled": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Enable or disable the pool.", + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + }, + }, + "algorithm": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The heart of a load balancer is its ability to effectively distribute traffic across healthy servers. If persistence is enabled, only the first connection from a client is load balanced. While the persistence remains in effect, subsequent connections or requests from a client are directed to the same server.", + Computed: true, + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString(string(edgeloadbalancer.PoolAlgorithmLeastConnections)), + Validators: []validator.String{ + fstringvalidator.OneOfWithDescription(func() (resp []fstringvalidator.OneOfWithDescriptionValues) { + x := []string{} + + for k := range edgeloadbalancer.PoolAlgorithms { + x = append(x, string(k)) + } + + slices.Sort(x) + + for _, v := range x { + resp = append(resp, fstringvalidator.OneOfWithDescriptionValues{ + Value: v, + Description: edgeloadbalancer.PoolAlgorithms[edgeloadbalancer.PoolAlgorithm(v)], + }) + } + return + }()...), + }, + }, + }, + "default_port": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ + MarkdownDescription: "DefaultPort defines destination server port used by the traffic sent to the member.", + }, + Resource: &schemaR.Int64Attribute{ + Required: true, + }, + DataSource: &schemaD.Int64Attribute{ + Computed: true, + }, + }, + "members": superschema.SuperSingleNestedAttributeOf[PoolModelMembers]{ + Common: &schemaR.SingleNestedAttribute{ + Description: "The members of the pool.", + }, + Resource: &schemaR.SingleNestedAttribute{ + Required: true, + }, + DataSource: &schemaD.SingleNestedAttribute{ + Computed: true, + }, + Attributes: superschema.Attributes{ + "graceful_timeout_period": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "Maximum time (in minutes) to gracefully disable a member. Virtual service waits for the specified time before terminating the existing connections to the members that are disabled. Special values: `0` represents `Immediate` and `-1` represents `Infinite`. The maximum value is `7200` minutes.", + Computed: true, + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString("1"), + }, + }, + "target_group": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The group contains reference to the Edge Firewall Group representing destination servers which are used by the Load Balancer Pool to direct load balanced traffic. This permit to reference `IP Set` or `Static Group` ID.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + fstringvalidator.PrefixContains(urn.SecurityGroup.String()), + stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("targets"), path.MatchRelative().AtParent().AtName("target_group")), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "targets": superschema.SuperListNestedAttributeOf[PoolModelMembersIPAddress]{ + Common: &schemaR.ListNestedAttribute{ + MarkdownDescription: "targets field defines list of destination servers which are used by the Load Balancer Pool to direct load balanced traffic.", + }, + Resource: &schemaR.ListNestedAttribute{ + Optional: true, + Validators: []validator.List{ + listvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("targets"), path.MatchRelative().AtParent().AtName("target_group")), + }, + }, + DataSource: &schemaD.ListNestedAttribute{ + Computed: true, + }, + Attributes: superschema.Attributes{ + "enabled": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Enable or disable the member.", + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + }, + }, + "ip_address": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The IP address of the member.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + Validators: []validator.String{ + fstringvalidator.IsNetwork([]fstringvalidator.NetworkValidatorType{ + fstringvalidator.IPV4, + }, + false, + ), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "port": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ + MarkdownDescription: "The port of the member.", + }, + Resource: &schemaR.Int64Attribute{ + Required: true, + }, + DataSource: &schemaD.Int64Attribute{ + Computed: true, + }, + }, + "ratio": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ + MarkdownDescription: "The ratio of the member. The ratio of each pool member denotes the traffic that goes to each server pool member. A server with a ratio of 2 gets twice as much traffic as a server with a ratio of 1.", + Computed: true, + }, + Resource: &schemaR.Int64Attribute{ + Optional: true, + Default: int64default.StaticInt64(1), + }, + }, + }, + }, + }, + }, + "health": superschema.SuperSingleNestedAttributeOf[PoolModelHealth]{ + Common: &schemaR.SingleNestedAttribute{ + Description: "Health check member servers health. It can be monitored by using one or more health monitors. Active monitors generate synthetic traffic and mark a server up or down based on the response.", + Computed: true, + }, + Resource: &schemaR.SingleNestedAttribute{ + Optional: true, + Default: objectdefault.StaticValue(supertypes.NewObjectValueOf[PoolModelHealth](ctx, &PoolModelHealth{ + PassiveMonitoringEnabled: supertypes.NewBoolValue(true), + Monitors: supertypes.NewListValueOfNull[string](ctx), + }).ObjectValue), + }, + Attributes: superschema.Attributes{ + "passive_monitoring_enabled": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "PassiveMonitoringEnabled sets if client traffic should be used to check if pool member is up or down.", + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(true), + }, + }, + "monitors": superschema.SuperListAttributeOf[string]{ + Common: &schemaR.ListAttribute{ + MarkdownDescription: "The active health monitors.", + }, + Resource: &schemaR.ListAttribute{ + Optional: true, + Validators: []validator.List{ + listvalidator.ValueStringsAre( + stringvalidator.OneOf(func() (resp []string) { + for _, v := range edgeloadbalancer.PoolHealthMonitorTypes { + resp = append(resp, string(v)) + } + slices.Sort(resp) + return + }()...), + ), + }, + }, + DataSource: &schemaD.ListAttribute{ + Computed: true, + }, + }, + }, + }, + "tls": superschema.SuperSingleNestedAttributeOf[PoolModelTLS]{ + Common: &schemaR.SingleNestedAttribute{ + Description: "The TLS configuration of the pool.", + Computed: true, + }, + Resource: &schemaR.SingleNestedAttribute{ + Optional: true, + Default: objectdefault.StaticValue(supertypes.NewObjectValueOf[PoolModelTLS](ctx, &PoolModelTLS{ + Enabled: supertypes.NewBoolValue(false), + DomainNames: supertypes.NewListValueOfNull[string](ctx), + CaCertificateRefs: supertypes.NewListValueOfNull[string](ctx), + CommonNameCheckEnabled: supertypes.NewBoolValue(false), + }).ObjectValue), + }, + Attributes: superschema.Attributes{ + "enabled": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Enable or disable the TLS.", + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + }, + }, + "domain_names": superschema.SuperListAttributeOf[string]{ + Common: &schemaR.ListAttribute{ + MarkdownDescription: "The domain names of the TLS check. This attribute is taken into account if the `common_name_check_enabled` is set to `true`.", + }, + Resource: &schemaR.ListAttribute{ + Optional: true, + Validators: []validator.List{ + listvalidator.SizeBetween(0, 10), + }, + }, + DataSource: &schemaD.ListAttribute{ + Computed: true, + }, + }, + "ca_certificate_refs": superschema.SuperListAttributeOf[string]{ + Common: &schemaR.ListAttribute{ + MarkdownDescription: "The CA certificate references point to root certificates to use when validating certificates presented by the pool members.", + }, + Resource: &schemaR.ListAttribute{ + MarkdownDescription: "Use `cloudavenue_org_certificate` resource to create a certificate and get the ID.", + Optional: true, + Validators: []validator.List{ + listvalidator.ValueStringsAre( + fstringvalidator.PrefixContains(urn.CertificateLibraryItem.String()), + ), + }, + }, + DataSource: &schemaD.ListAttribute{ + Computed: true, + }, + }, + "common_name_check_enabled": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Enable common name check for server certificate. If enabled and no explicit domain name is specified, the incoming host header will be used to do the match.", + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + }, + }, + }, + }, + "persistence": superschema.SuperSingleNestedAttributeOf[PoolModelPersistence]{ + Common: &schemaR.SingleNestedAttribute{ + Description: "Persistence profile will ensure that the same user sticks to the same server for a desired duration of time. If the persistence profile is unmanaged by ELB, updates that leave the values unchanged will continue to use the same unmanaged profile. Any changes made to the persistence profile will cause ELB to switch the pool to a profile managed by ELB.", + Computed: true, + }, + Resource: &schemaR.SingleNestedAttribute{ + Optional: true, + Default: objectdefault.StaticValue(supertypes.NewObjectValueOf[PoolModelPersistence](ctx, &PoolModelPersistence{ + Type: supertypes.NewStringValue(string(edgeloadbalancer.PoolPersistenceProfileTypeClientIP)), + Value: supertypes.NewStringNull(), + }).ObjectValue), + }, + Attributes: superschema.Attributes{ + "type": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The type of the persistence.", + Computed: true, + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Default: stringdefault.StaticString(string(edgeloadbalancer.PoolPersistenceProfileTypeClientIP)), + Validators: []validator.String{ + fstringvalidator.OneOfWithDescription(func() (resp []fstringvalidator.OneOfWithDescriptionValues) { + x := []string{} + + for k := range edgeloadbalancer.PoolPersistenceProfileTypes { + x = append(x, string(k)) + } + + slices.Sort(x) + + for _, v := range x { + resp = append(resp, fstringvalidator.OneOfWithDescriptionValues{ + Value: v, + Description: edgeloadbalancer.PoolPersistenceProfileTypes[edgeloadbalancer.PoolPersistenceProfileType(v)], + }) + } + return + }()...), + }, + }, + }, + "value": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The value of the persistence.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + fstringvalidator.RequireIfAttributeIsOneOf(path.MatchRelative().AtParent().AtName("type"), func() (resp []attr.Value) { + resp = make([]attr.Value, 0) + resp = append(resp, types.StringValue(string(edgeloadbalancer.PoolPersistenceProfileTypeHTTPCookie))) + resp = append(resp, types.StringValue(string(edgeloadbalancer.PoolPersistenceProfileTypeCustomHTTPHeader))) + resp = append(resp, types.StringValue(string(edgeloadbalancer.PoolPersistenceProfileTypeAPPCookie))) + return + }()), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + } +} diff --git a/internal/provider/elb/pool_schema_test.go b/internal/provider/elb/pool_schema_test.go new file mode 100644 index 00000000..dedfbb6a --- /dev/null +++ b/internal/provider/elb/pool_schema_test.go @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb_test + +import ( + "context" + "testing" + + // fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource". + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/elb" +) + +// Unit test for the schema of the resource cloudavenue_elb_pool. +func TestPoolResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + elb.NewPoolResource().Schema(ctx, fwresource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} + +// Unit test for the schema of the datasource cloudavenue_elb_pool + +func TestPoolDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + elb.NewPoolDataSource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/elb/pool_types.go b/internal/provider/elb/pool_types.go new file mode 100644 index 00000000..8c0933a4 --- /dev/null +++ b/internal/provider/elb/pool_types.go @@ -0,0 +1,205 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + "strconv" + + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + govcdtypes "github.com/vmware/go-vcloud-director/v2/types/v56" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type ( + PoolModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + Description supertypes.StringValue `tfsdk:"description"` + EdgeGatewayID supertypes.StringValue `tfsdk:"edge_gateway_id"` + EdgeGatewayName supertypes.StringValue `tfsdk:"edge_gateway_name"` + Enabled supertypes.BoolValue `tfsdk:"enabled"` + Algorithm supertypes.StringValue `tfsdk:"algorithm"` + DefaultPort supertypes.Int64Value `tfsdk:"default_port"` + Members supertypes.SingleNestedObjectValueOf[PoolModelMembers] `tfsdk:"members"` + Health supertypes.SingleNestedObjectValueOf[PoolModelHealth] `tfsdk:"health"` + TLS supertypes.SingleNestedObjectValueOf[PoolModelTLS] `tfsdk:"tls"` + Persistence supertypes.SingleNestedObjectValueOf[PoolModelPersistence] `tfsdk:"persistence"` + } + + PoolModelMembers struct { + GracefulTimeoutPeriod supertypes.StringValue `tfsdk:"graceful_timeout_period"` + TargetGroup supertypes.StringValue `tfsdk:"target_group"` + Targets supertypes.ListNestedObjectValueOf[PoolModelMembersIPAddress] `tfsdk:"targets"` + } + + PoolModelMembersIPAddress struct { + Enabled supertypes.BoolValue `tfsdk:"enabled"` + IPAddress supertypes.StringValue `tfsdk:"ip_address"` + Port supertypes.Int64Value `tfsdk:"port"` + Ratio supertypes.Int64Value `tfsdk:"ratio"` + } + + PoolModelHealth struct { + PassiveMonitoringEnabled supertypes.BoolValue `tfsdk:"passive_monitoring_enabled"` + Monitors supertypes.ListValueOf[string] `tfsdk:"monitors"` + } + + PoolModelTLS struct { + Enabled supertypes.BoolValue `tfsdk:"enabled"` + DomainNames supertypes.ListValueOf[string] `tfsdk:"domain_names"` + CaCertificateRefs supertypes.ListValueOf[string] `tfsdk:"ca_certificate_refs"` + CommonNameCheckEnabled supertypes.BoolValue `tfsdk:"common_name_check_enabled"` + } + + PoolModelPersistence struct { + Type supertypes.StringValue `tfsdk:"type"` + Value supertypes.StringValue `tfsdk:"value"` + } +) + +func (rm *PoolModel) Copy() *PoolModel { + x := &PoolModel{} + utils.ModelCopy(rm, x) + return x +} + +// ToSDKPoolGroupModel converts the model to the SDK model. +func (rm *PoolModel) ToSDKPoolModelRequest(ctx context.Context, cavClient *client.CloudAvenue) (*edgeloadbalancer.PoolModelRequest, diag.Diagnostics) { + var diags diag.Diagnostics + + pool := &edgeloadbalancer.PoolModelRequest{ + Name: rm.Name.Get(), + Description: rm.Description.Get(), + Enabled: rm.Enabled.GetPtr(), + Algorithm: edgeloadbalancer.PoolAlgorithm(rm.Algorithm.Get()), + DefaultPort: rm.DefaultPort.GetIntPtr(), + GatewayRef: govcdtypes.OpenApiReference{ID: rm.EdgeGatewayID.Get(), Name: rm.EdgeGatewayName.Get()}, + } + + if rm.Members.IsKnown() { + x, d := rm.Members.Get(ctx) + diags = append(diags, d...) + if diags.HasError() { + return nil, diags + } + + if x.GracefulTimeoutPeriod.IsKnown() { + i, err := strconv.Atoi(x.GracefulTimeoutPeriod.Get()) + if err != nil { + diags.AddError("Error converting GracefulTimeoutPeriod to int", err.Error()) + return nil, diags + } + pool.GracefulTimeoutPeriod = &i + } + + if x.TargetGroup.IsKnown() { + pool.MemberGroupRef = &govcdtypes.OpenApiReference{ + ID: x.TargetGroup.Get(), + } + } + + if x.Targets.IsKnown() { + ipAddrs, d := x.Targets.Get(ctx) + diags = append(diags, d...) + if diags.HasError() { + return nil, diags + } + + members := make([]edgeloadbalancer.PoolModelMember, 0) + for _, m := range ipAddrs { + members = append(members, edgeloadbalancer.PoolModelMember{ + Enabled: m.Enabled.Get(), + IPAddress: m.IPAddress.Get(), + Port: m.Port.GetInt(), + Ratio: m.Ratio.GetIntPtr(), + }) + } + pool.Members = members + } + } + + if rm.Health.IsKnown() { + x, d := rm.Health.Get(ctx) + diags = append(diags, d...) + if diags.HasError() { + return nil, diags + } + + pool.PassiveMonitoringEnabled = x.PassiveMonitoringEnabled.GetPtr() + if x.Monitors.IsKnown() { + pool.HealthMonitors = make([]edgeloadbalancer.PoolModelHealthMonitor, 0) + monitors, d := x.Monitors.Get(ctx) + diags.Append(d...) + if diags.HasError() { + return nil, diags + } + + for _, m := range monitors { + pool.HealthMonitors = append(pool.HealthMonitors, edgeloadbalancer.PoolModelHealthMonitor{ + Type: edgeloadbalancer.PoolHealthMonitorType(m), + }) + } + } + } + + if rm.TLS.IsKnown() { + x, d := rm.TLS.Get(ctx) + diags = append(diags, d...) + if diags.HasError() { + return nil, diags + } + + pool.SSLEnabled = x.Enabled.GetPtr() + pool.CommonNameCheckEnabled = x.CommonNameCheckEnabled.GetPtr() + if x.DomainNames.IsKnown() { + pool.DomainNames, d = x.DomainNames.Get(ctx) + diags.Append(d...) + if diags.HasError() { + return nil, diags + } + } + + if x.CaCertificateRefs.IsKnown() { + refs, d := x.CaCertificateRefs.Get(ctx) + diags.Append(d...) + if diags.HasError() { + return nil, diags + } + + for _, ref := range refs { + pool.CaCertificateRefs = append(pool.CaCertificateRefs, govcdtypes.OpenApiReference{ + ID: ref, + }) + } + } + } + + if rm.Persistence.IsKnown() { + x, d := rm.Persistence.Get(ctx) + diags = append(diags, d...) + if diags.HasError() { + return nil, diags + } + + pool.PersistenceProfile = &edgeloadbalancer.PoolModelPersistenceProfile{ + Type: edgeloadbalancer.PoolPersistenceProfileType(x.Type.Get()), + Value: x.Value.Get(), + } + } + + return pool, diags +} diff --git a/internal/provider/elb/service_engine_group_datasource.go b/internal/provider/elb/service_engine_group_datasource.go new file mode 100644 index 00000000..6e13259a --- /dev/null +++ b/internal/provider/elb/service_engine_group_datasource.go @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +// package elb provides a Terraform datasource. +package elb + +import ( + "context" + "fmt" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + commoncloudavenue "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/common/cloudavenue" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +var ( + _ datasource.DataSource = &serviceEngineGroupDataSource{} + _ datasource.DataSourceWithConfigure = &serviceEngineGroupDataSource{} +) + +func NewServiceEngineGroupDataSource() datasource.DataSource { + return &serviceEngineGroupDataSource{} +} + +type serviceEngineGroupDataSource struct { + client *client.CloudAvenue + edgegwlb edgeloadbalancer.Client +} + +// Init Initializes the data source. +func (d *serviceEngineGroupDataSource) Init(ctx context.Context, dm *serviceEngineGroupModel) (diags diag.Diagnostics) { + var err error + + d.edgegwlb, err = edgeloadbalancer.NewClient() + if err != nil { + diags.AddError("Error creating edge load balancer client", err.Error()) + } + + return +} + +func (d *serviceEngineGroupDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_service_engine_group" +} + +func (d *serviceEngineGroupDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = serviceEngineGroupSchema(ctx).GetDataSource(ctx) +} + +func (d *serviceEngineGroupDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *serviceEngineGroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_elb_service_engine_group", d.client.GetOrgName(), metrics.Read)() + + config := &serviceEngineGroupModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + // Read data from the API + data, found, diags := d.read(ctx, config) + if !found { + diags.AddError("Error Not Found", "The Service Engine Group was not found") + } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *serviceEngineGroupDataSource) read(ctx context.Context, dm *serviceEngineGroupModel) (data *serviceEngineGroupModel, found bool, diags diag.Diagnostics) { + data = &serviceEngineGroupModel{} + + // Get ServiceEngineGroup + var ( + err error + albSEG *edgeloadbalancer.ServiceEngineGroupModel + ) + + if !dm.EdgeGatewayID.IsKnown() { + edge, err := d.client.CAVSDK.V1.EdgeGateway.Get(dm.EdgeGatewayName.Get()) + if err != nil { + diags.AddError("Error retrieving Edge Gateway", err.Error()) + // True because there was an error on fetch the edge gateway and not the service engine group + return nil, true, diags + } + + dm.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, edge.GetID()).String()) + } + + if dm.ID.IsKnown() { + albSEG, err = d.edgegwlb.GetServiceEngineGroup(ctx, dm.EdgeGatewayID.Get(), dm.ID.Get()) + } else { + albSEG, err = d.edgegwlb.GetServiceEngineGroup(ctx, dm.EdgeGatewayID.Get(), dm.Name.Get()) + } + if err != nil { + if commoncloudavenue.IsNotFound(err) || govcd.IsNotFound(err) { + return nil, false, diags + } + diags.AddError("Error retrieving Service Engine Group", err.Error()) + return nil, true, diags + } + + data.ID.Set(albSEG.ID) + data.Name.Set(albSEG.Name) + data.EdgeGatewayID.Set(albSEG.GatewayRef.ID) + data.EdgeGatewayName.Set(albSEG.GatewayRef.Name) + data.MaxVirtualServices.SetIntPtr(albSEG.MaxVirtualServices) + data.ReservedVirtualServices.SetIntPtr(albSEG.MinVirtualServices) + data.DeployedVirtualServices.SetInt(albSEG.NumDeployedVirtualServices) + + return data, true, diags +} diff --git a/internal/provider/elb/service_engine_group_schema.go b/internal/provider/elb/service_engine_group_schema.go new file mode 100644 index 00000000..e5dd7004 --- /dev/null +++ b/internal/provider/elb/service_engine_group_schema.go @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + + superschema "github.com/orange-cloudavenue/terraform-plugin-framework-superschema" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func serviceEngineGroupSchema(_ context.Context) superschema.Schema { + return superschema.Schema{ + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_elb_service_engine_group` data source allows you to retrieve information about an Service Engine Group of an Edge Gateway.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + Computed: true, + Optional: true, + MarkdownDescription: "The ID of the ELB Service Engine Group.", + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), + }, + }, + }, + "name": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + MarkdownDescription: "The name of the ELB Service Engine Group.", + Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), + }, + }, + }, + "edge_gateway_id": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + MarkdownDescription: "Edge gateway ID in which ELB Service Engine Group should be located.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_id"), path.MatchRoot("edge_gateway_name")), + }, + Computed: true, + }, + }, + "edge_gateway_name": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + MarkdownDescription: "Edge gateway Name in which ELB Service Engine Group should be located.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_id"), path.MatchRoot("edge_gateway_name")), + }, + Computed: true, + }, + }, + "max_virtual_services": superschema.SuperInt64Attribute{ + DataSource: &schemaD.Int64Attribute{ + Computed: true, + MarkdownDescription: "The maximum number of virtual services that can be deployed on the ELB Service Engine Group.", + }, + }, + "reserved_virtual_services": superschema.SuperInt64Attribute{ + DataSource: &schemaD.Int64Attribute{ + Computed: true, + MarkdownDescription: "The number of reserved virtual services for the ELB Service Engine Group.", + }, + }, + "deployed_virtual_services": superschema.SuperInt64Attribute{ + DataSource: &schemaD.Int64Attribute{ + Computed: true, + MarkdownDescription: "The number of deployed virtual services on the ELB Service Engine Group.", + }, + }, + }, + } +} diff --git a/internal/provider/elb/service_engine_group_schema_test.go b/internal/provider/elb/service_engine_group_schema_test.go new file mode 100644 index 00000000..5ebbe4dd --- /dev/null +++ b/internal/provider/elb/service_engine_group_schema_test.go @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb_test + +import ( + "context" + "testing" + + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/elb" +) + +// TODO : Comment or uncomment the following imports if you are using resources or/and datasources +/* +// Unit test for the schema of the resource cloudavenue_alb_AlbServiceEngineGroupDatasourceName +func TestAlbServiceEngineGroupDatasourceNameResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + alb.NewAlbServiceEngineGroupDatasourceNameResource().Schema(ctx, fwresource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} +*/ +// Unit test for the schema of the datasource cloudavenue_alb_AlbServiceEngineGroupDatasourceName + +func TestALBServiceEngineGroupDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + elb.NewServiceEngineGroupDataSource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/elb/service_engine_group_types.go b/internal/provider/elb/service_engine_group_types.go new file mode 100644 index 00000000..886c98a9 --- /dev/null +++ b/internal/provider/elb/service_engine_group_types.go @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" +) + +type serviceEngineGroupModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + EdgeGatewayID supertypes.StringValue `tfsdk:"edge_gateway_id"` + EdgeGatewayName supertypes.StringValue `tfsdk:"edge_gateway_name"` + MaxVirtualServices supertypes.Int64Value `tfsdk:"max_virtual_services"` + ReservedVirtualServices supertypes.Int64Value `tfsdk:"reserved_virtual_services"` + DeployedVirtualServices supertypes.Int64Value `tfsdk:"deployed_virtual_services"` +} diff --git a/internal/provider/elb/service_engine_groups_datasource.go b/internal/provider/elb/service_engine_groups_datasource.go new file mode 100644 index 00000000..e51ae5c0 --- /dev/null +++ b/internal/provider/elb/service_engine_groups_datasource.go @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +// package elb provides a Terraform datasource. +package elb + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +var ( + _ datasource.DataSource = &serviceEngineGroupsDataSource{} + _ datasource.DataSourceWithConfigure = &serviceEngineGroupsDataSource{} +) + +func NewServiceEngineGroupsDataSource() datasource.DataSource { + return &serviceEngineGroupsDataSource{} +} + +type serviceEngineGroupsDataSource struct { + client *client.CloudAvenue + edgegwlb edgeloadbalancer.Client +} + +// Init Initializes the data source. +func (d *serviceEngineGroupsDataSource) Init(ctx context.Context, dm *serviceEngineGroupsModel) (diags diag.Diagnostics) { + var err error + + d.edgegwlb, err = edgeloadbalancer.NewClient() + if err != nil { + diags.AddError("Error creating edge load balancer client", err.Error()) + } + + return +} + +func (d *serviceEngineGroupsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_service_engine_groups" +} + +func (d *serviceEngineGroupsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = serviceEngineGroupsSchema(ctx).GetDataSource(ctx) +} + +func (d *serviceEngineGroupsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *serviceEngineGroupsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_elb_service_engine_groups", d.client.GetOrgName(), metrics.Read)() + + config := &serviceEngineGroupsModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + // Get ServiceEngineGroup + edge, err := d.client.CAVSDK.V1.EdgeGateway.Get(func() string { + if config.EdgeGatewayID.IsKnown() { + return config.EdgeGatewayID.Get() + } + + return config.EdgeGatewayName.Get() + }()) + if err != nil { + resp.Diagnostics.AddError("Error retrieving Edge Gateway", err.Error()) + return + } + + config.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, edge.GetID()).String()) + config.EdgeGatewayName.Set(edge.GetName()) + + // Get Service Engine Groups + albSEGs, err := d.edgegwlb.ListServiceEngineGroups(ctx, config.EdgeGatewayID.Get()) + if err != nil { + resp.Diagnostics.AddError("Error retrieving Service Engine Groups", err.Error()) + return + } + + // Set SDK model to Terraform model + seg := make([]*serviceEngineGroupModel, 0) + for _, albSEG := range albSEGs { + x := &serviceEngineGroupModel{} + x.ID.Set(albSEG.ID) + x.Name.Set(albSEG.Name) + x.EdgeGatewayID.Set(albSEG.GatewayRef.ID) + x.EdgeGatewayName.Set(albSEG.GatewayRef.Name) + x.MaxVirtualServices.SetIntPtr(albSEG.MaxVirtualServices) + x.ReservedVirtualServices.SetIntPtr(albSEG.MinVirtualServices) + x.DeployedVirtualServices.SetInt(albSEG.NumDeployedVirtualServices) + seg = append(seg, x) + } + + // Set config + resp.Diagnostics.Append(config.ServiceEngineGroups.Set(ctx, seg)...) + if resp.Diagnostics.HasError() { + return + } + + config.ID.Set(urn.Normalize(urn.Gateway, config.EdgeGatewayID.Get()).String()) + // Set state + resp.Diagnostics.Append(resp.State.Set(ctx, config)...) +} diff --git a/internal/provider/elb/service_engine_groups_schema.go b/internal/provider/elb/service_engine_groups_schema.go new file mode 100644 index 00000000..c492e422 --- /dev/null +++ b/internal/provider/elb/service_engine_groups_schema.go @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + + superschema "github.com/orange-cloudavenue/terraform-plugin-framework-superschema" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func serviceEngineGroupsSchema(ctx context.Context) superschema.Schema { + return superschema.Schema{ + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_elb_service_engine_groups` data source allows you to retrieve information about all the Service Engine Group of an Edge Gateway.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the ELB service engine groups.", + }, + }, + "edge_gateway_id": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + MarkdownDescription: "Edge gateway ID in which EdgeGateway LoadBalancer Service Engine Group should be located.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_id"), path.MatchRoot("edge_gateway_name")), + }, + Computed: true, + }, + }, + "edge_gateway_name": superschema.SuperStringAttribute{ + DataSource: &schemaD.StringAttribute{ + MarkdownDescription: "Edge gateway Name in which EdgeGateway LoadBalancer Service Engine Group should be located.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_id"), path.MatchRoot("edge_gateway_name")), + }, + Computed: true, + }, + }, + "service_engine_groups": superschema.SuperListNestedAttributeOf[serviceEngineGroupModel]{ + DataSource: &schemaD.ListNestedAttribute{ + Computed: true, + MarkdownDescription: "The list of service engine groups.", + }, + Attributes: map[string]superschema.Attribute{ + "id": serviceEngineGroupSchema(ctx).Attributes["id"], + "name": serviceEngineGroupSchema(ctx).Attributes["name"], + "edge_gateway_id": serviceEngineGroupSchema(ctx).Attributes["edge_gateway_id"], + "edge_gateway_name": serviceEngineGroupSchema(ctx).Attributes["edge_gateway_name"], + "max_virtual_services": serviceEngineGroupSchema(ctx).Attributes["max_virtual_services"], + "reserved_virtual_services": serviceEngineGroupSchema(ctx).Attributes["reserved_virtual_services"], + "deployed_virtual_services": serviceEngineGroupSchema(ctx).Attributes["deployed_virtual_services"], + }, + }, + }, + } +} diff --git a/internal/provider/elb/service_engine_groups_schema_test.go b/internal/provider/elb/service_engine_groups_schema_test.go new file mode 100644 index 00000000..9ccadfa7 --- /dev/null +++ b/internal/provider/elb/service_engine_groups_schema_test.go @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb_test + +import ( + "context" + "testing" + + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/elb" +) + +// Unit test for the schema of the datasource cloudavenue_alb_ServiceEngineGroups. +func TestServiceEngineGroupsDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + elb.NewServiceEngineGroupsDataSource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/elb/service_engine_groups_types.go b/internal/provider/elb/service_engine_groups_types.go new file mode 100644 index 00000000..bf8eb750 --- /dev/null +++ b/internal/provider/elb/service_engine_groups_types.go @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + +type serviceEngineGroupsModel struct { + ID supertypes.StringValue `tfsdk:"id"` + ServiceEngineGroups supertypes.ListNestedObjectValueOf[serviceEngineGroupModel] `tfsdk:"service_engine_groups"` + EdgeGatewayID supertypes.StringValue `tfsdk:"edge_gateway_id"` + EdgeGatewayName supertypes.StringValue `tfsdk:"edge_gateway_name"` +} diff --git a/internal/provider/elb/virtual_service_datasource.go b/internal/provider/elb/virtual_service_datasource.go new file mode 100644 index 00000000..1e9f193f --- /dev/null +++ b/internal/provider/elb/virtual_service_datasource.go @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +// Package elb provides a Terraform datasource. +package elb + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + v1 "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +var ( + _ datasource.DataSource = &VirtualServiceDataSource{} + _ datasource.DataSourceWithConfigure = &VirtualServiceDataSource{} +) + +func NewVirtualServiceDataSource() datasource.DataSource { + return &VirtualServiceDataSource{} +} + +type VirtualServiceDataSource struct { + client *client.CloudAvenue + elb edgeloadbalancer.Client + edge *v1.EdgeClient +} + +// Init Initializes the data source. +func (d *VirtualServiceDataSource) Init(ctx context.Context, dm *VirtualServiceModel) (diags diag.Diagnostics) { + var err error + + d.elb, err = edgeloadbalancer.NewClient() + if err != nil { + diags.AddError("Error creating elb client", err.Error()) + } + + eIDOrName := dm.EdgeGatewayID.Get() + if eIDOrName == "" { + eIDOrName = dm.EdgeGatewayName.Get() + } + d.edge, err = d.client.CAVSDK.V1.EdgeGateway.Get(eIDOrName) + if err != nil { + diags.AddError("Error creating edge client", err.Error()) + } + + dm.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, d.edge.GetID()).String()) + dm.EdgeGatewayName.Set(d.edge.GetName()) + + return +} + +func (d *VirtualServiceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_virtual_service" +} + +func (d *VirtualServiceDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = virtualServiceSchema(ctx).GetDataSource(ctx) +} + +func (d *VirtualServiceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *VirtualServiceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_elb_virtual_service", d.client.GetOrgName(), metrics.Read)() + + config := &VirtualServiceModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + s := &VirtualServiceResource{ + client: d.client, + elb: d.elb, + edge: d.edge, + } + + // Read data from the API + data, found, diags := s.read(ctx, config) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found") + return + } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/elb/virtual_service_resource.go b/internal/provider/elb/virtual_service_resource.go new file mode 100644 index 00000000..bca48404 --- /dev/null +++ b/internal/provider/elb/virtual_service_resource.go @@ -0,0 +1,397 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + "fmt" + "strings" + + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + v1 "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/mutex" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &VirtualServiceResource{} + _ resource.ResourceWithConfigure = &VirtualServiceResource{} + _ resource.ResourceWithImportState = &VirtualServiceResource{} + // _ resource.ResourceWithModifyPlan = &VirtualServiceResource{} + // _ resource.ResourceWithUpgradeState = &VirtualServiceResource{} + // _ resource.ResourceWithValidateConfig = &VirtualServiceResource{}. +) + +// NewVirtualServiceResource is a helper function to simplify the provider implementation. +func NewVirtualServiceResource() resource.Resource { + return &VirtualServiceResource{} +} + +// VirtualServiceResource is the resource implementation. +type VirtualServiceResource struct { + client *client.CloudAvenue + elb edgeloadbalancer.Client + edge *v1.EdgeClient +} + +// Init Initializes the resource. +func (r *VirtualServiceResource) Init(ctx context.Context, rm *VirtualServiceModel) (diags diag.Diagnostics) { + var err error + + r.elb, err = edgeloadbalancer.NewClient() + if err != nil { + diags.AddError("Error creating elb client", err.Error()) + } + + eIDOrName := rm.EdgeGatewayID.Get() + if eIDOrName == "" { + eIDOrName = rm.EdgeGatewayName.Get() + } + r.edge, err = r.client.CAVSDK.V1.EdgeGateway.Get(eIDOrName) + if err != nil { + diags.AddError("Error creating edge client", err.Error()) + } + + rm.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, r.edge.GetID()).String()) + rm.EdgeGatewayName.Set(r.edge.GetName()) + + return +} + +// Metadata returns the resource type name. +func (r *VirtualServiceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_virtual_service" +} + +// Schema defines the schema for the resource. +func (r *VirtualServiceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = virtualServiceSchema(ctx).GetResource(ctx) +} + +func (r *VirtualServiceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +// Create creates the resource and sets the initial Terraform state. +func (r *VirtualServiceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer metrics.New("cloudavenue_elb_virtual_service", r.client.GetOrgName(), metrics.Create)() + + plan := &VirtualServiceModel{} + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource creation logic here. + */ + + mutex.GlobalMutex.KvLock(ctx, plan.EdgeGatewayID.Get()) + defer mutex.GlobalMutex.KvUnlock(ctx, plan.EdgeGatewayID.Get()) + + modelRequest, d := plan.ToSDKVirtualServiceModelRequest(ctx, r.elb) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + vsCreated, err := r.elb.CreateVirtualService(ctx, *modelRequest) + if err != nil { + resp.Diagnostics.AddError("Error creating virtual service", err.Error()) + return + } + + plan.ID.Set(vsCreated.ID) + + // Use generic read function to refresh the state + state, found, d := r.read(ctx, plan) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found after creation") + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *VirtualServiceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer metrics.New("cloudavenue_elb_virtual_service", r.client.GetOrgName(), metrics.Read)() + + state := &VirtualServiceModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Refresh the state + stateRefreshed, found, d := r.read(ctx, state) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found after refresh") + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *VirtualServiceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer metrics.New("cloudavenue_elb_virtual_service", r.client.GetOrgName(), metrics.Update)() + + var ( + plan = &VirtualServiceModel{} + state = &VirtualServiceModel{} + ) + + // Get current plan and state + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource update here + */ + + mutex.GlobalMutex.KvLock(ctx, plan.EdgeGatewayID.Get()) + defer mutex.GlobalMutex.KvUnlock(ctx, plan.EdgeGatewayID.Get()) + + modelRequest, d := plan.ToSDKVirtualServiceModelRequest(ctx, r.elb) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + _, err := r.elb.UpdateVirtualService(ctx, state.ID.Get(), *modelRequest) + if err != nil { + resp.Diagnostics.AddError("Error updating virtual service", err.Error()) + return + } + + // Use generic read function to refresh the state + stateRefreshed, found, d := r.read(ctx, plan) + if !found { + resp.Diagnostics.AddError("Resource not found", "The resource was not found after update") + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *VirtualServiceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer metrics.New("cloudavenue_elb_virtual_service", r.client.GetOrgName(), metrics.Delete)() + + state := &VirtualServiceModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource deletion here + */ + + mutex.GlobalMutex.KvLock(ctx, state.EdgeGatewayID.Get()) + defer mutex.GlobalMutex.KvUnlock(ctx, state.EdgeGatewayID.Get()) + + if err := r.elb.DeleteVirtualService(ctx, state.ID.Get()); err != nil { + resp.Diagnostics.AddError("Error deleting virtual service", err.Error()) + } +} + +func (r *VirtualServiceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer metrics.New("cloudavenue_elb_virtual_service", r.client.GetOrgName(), metrics.Import)() + + // Import format is edgeGatewayIDOrName.virtualServiceIDOrName + + // * Import with custom logic + idParts := strings.Split(req.ID, ".") + + if len(idParts) != 2 { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: edgeGatewayIDOrName.virtualServiceIDOrName. Got: %q", req.ID), + ) + return + } + + x := &VirtualServiceModel{ + ID: supertypes.NewStringNull(), + Name: supertypes.NewStringNull(), + EdgeGatewayID: supertypes.NewStringNull(), + EdgeGatewayName: supertypes.NewStringNull(), + } + + if urn.IsEdgeGateway(idParts[0]) { + x.EdgeGatewayID.Set(idParts[0]) + } else { + edge, err := r.client.CAVSDK.V1.EdgeGateway.Get(idParts[0]) + if err != nil { + resp.Diagnostics.AddError("Error retrieving Edge Gateway", err.Error()) + return + } + x.EdgeGatewayName.Set(idParts[0]) + x.EdgeGatewayID.Set(urn.Normalize(urn.Gateway, edge.GetID()).String()) + } + + if urn.IsLoadBalancerVirtualService(idParts[1]) { + x.ID.Set(idParts[1]) + } else { + x.Name.Set(idParts[1]) + } + + resp.Diagnostics.Append(r.Init(ctx, x)...) + if resp.Diagnostics.HasError() { + return + } + + stateRefreshed, found, d := r.read(ctx, x) + if !found { + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// * CustomFuncs + +// read is a generic read function that can be used by the resource Create, Read and Update functions. +func (r *VirtualServiceResource) read(ctx context.Context, planOrState *VirtualServiceModel) (stateRefreshed *VirtualServiceModel, found bool, diags diag.Diagnostics) { + stateRefreshed = planOrState.Copy() + + /* + Implement the resource read here + */ + + nameOrID := planOrState.Name.Get() + if planOrState.ID.IsKnown() { + nameOrID = planOrState.ID.Get() + } + + data, err := r.elb.GetVirtualService(ctx, stateRefreshed.EdgeGatewayID.Get(), nameOrID) + if err != nil { + if govcd.ContainsNotFound(err) { + return nil, false, nil + } + diags.AddError("Error retrieving virtual service", err.Error()) + return nil, true, diags + } + + stateRefreshed.ID.Set(data.ID) + stateRefreshed.Name.Set(data.Name) + stateRefreshed.Description.Set(data.Description) + stateRefreshed.Enabled.SetPtr(data.Enabled) + stateRefreshed.EdgeGatewayID.Set(data.EdgeGatewayRef.ID) + stateRefreshed.EdgeGatewayName.Set(data.EdgeGatewayRef.Name) + stateRefreshed.PoolID.Set(data.PoolRef.ID) + stateRefreshed.PoolName.Set(data.PoolRef.Name) + stateRefreshed.ServiceEngineGroupName.Set(data.ServiceEngineGroupRef.Name) + stateRefreshed.VirtualIP.Set(data.VirtualIPAddress) + stateRefreshed.ServiceType.Set(string(data.ApplicationProfile)) + + if data.CertificateRef != nil { + stateRefreshed.CertificateID.SetPtr(&data.CertificateRef.ID) + } else { + stateRefreshed.CertificateID.SetNull() + } + + servicePorts := make([]*VirtualServiceModelServicePort, 0) + for _, port := range data.ServicePorts { + sp := &VirtualServiceModelServicePort{ + Start: supertypes.NewInt64Null(), + End: supertypes.NewInt64Null(), + } + + sp.Start.SetIntPtr(port.Start) + sp.End.SetIntPtr(port.End) + servicePorts = append(servicePorts, sp) + } + + diags.Append(stateRefreshed.ServicePorts.Set(ctx, servicePorts)...) + + return stateRefreshed, true, diags +} diff --git a/internal/provider/elb/virtual_service_schema.go b/internal/provider/elb/virtual_service_schema.go new file mode 100644 index 00000000..e4074d19 --- /dev/null +++ b/internal/provider/elb/virtual_service_schema.go @@ -0,0 +1,238 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + + superschema "github.com/orange-cloudavenue/terraform-plugin-framework-superschema" + fstringvalidator "github.com/orange-cloudavenue/terraform-plugin-framework-validators/stringvalidator" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + schemaR "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" +) + +func virtualServiceSchema(_ context.Context) superschema.Schema { + return superschema.Schema{ + Resource: superschema.SchemaDetails{ + MarkdownDescription: "Provides a resource to manage ELB Virtual services for particular Gateway. A virtual service advertises an IP address and ports to the external world and listens for client traffic. When a virtual service receives traffic, it directs it to members in ELB Pool.", + }, + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "Provides a data source to read ELB Virtual services for particular Gateway. A virtual service advertises an IP address and ports to the external world and listens for client traffic. When a virtual service receives traffic, it directs it to members in ELB Pool.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the ELB virtual service.", + }, + Resource: &schemaR.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + "name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the ELB Virtual Service.", + Required: true, + }, + }, + "edge_gateway_name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the edge gateway on which the ELB Virtual Service is to be created.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_name"), path.MatchRoot("edge_gateway_id")), + }, + }, + }, + "edge_gateway_id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The ID of the edge gateway on which the ELB Virtual Service is to be created.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("edge_gateway_name"), path.MatchRoot("edge_gateway_id")), + }, + }, + }, + "description": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The description of the ELB Virtual Service.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "enabled": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Defines if the ELB Virtual Service is enabled.", + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + DataSource: &schemaD.BoolAttribute{ + Computed: true, + }, + }, + "pool_name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the ELB Server Pool associated.", + Computed: true, + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("pool_name"), path.MatchRoot("pool_id")), + }, + }, + }, + "pool_id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The ID of the ELB Server Pool associated.", + Computed: true, + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("pool_name"), path.MatchRoot("pool_id")), + }, + }, + }, + "service_engine_group_name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the service Engine Group (Take the first one if not specified).", + Computed: true, + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + }, + "virtual_ip": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The virtual IP address of the ELB Virtual Service.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + MarkdownDescription: "\n\n -> The `virtual_ip` accept a private IP in your network range or a public IP (Warning: the public IP must be reserved and not used by any other service).", + Validators: []validator.String{ + fstringvalidator.IsNetwork([]fstringvalidator.NetworkValidatorType{fstringvalidator.IPV4}, false), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "service_type": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The type of the service. The different modes that the ELB supports for handling TCP traffic and various parameters that can be tuned for optimization of the TCP traffic are also detailed here.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(func() []string { + var values []string + for _, v := range edgeloadbalancer.VirtualServiceApplicationProfiles { + values = append(values, string(v)) + } + return values + }()...), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "certificate_id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The ID of the certificate. The certificate must be uploaded to your certificate library before it can be used. The certificate MUSTN'T be expired.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + fstringvalidator.RequireIfAttributeIsOneOf(path.MatchRelative().AtParent().AtName("service_type"), []attr.Value{types.StringValue("L4_TLS"), types.StringValue("HTTPS")}), + fstringvalidator.NullIfAttributeIsOneOf(path.MatchRelative().AtParent().AtName("service_type"), []attr.Value{types.StringValue("HTTP"), types.StringValue("L4_TCP"), types.StringValue("L4_UDP")}), + fstringvalidator.PrefixContains(urn.CertificateLibraryItem.String()), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "service_ports": superschema.SuperListNestedAttributeOf[VirtualServiceModelServicePort]{ + Common: &schemaR.ListNestedAttribute{ + MarkdownDescription: "The service port of the ELB Virtual Service. The service port is the port on which the virtual service listens for client traffic.", + }, + Resource: &schemaR.ListNestedAttribute{ + Required: true, + }, + DataSource: &schemaD.ListNestedAttribute{ + Computed: true, + }, + Attributes: map[string]superschema.Attribute{ + "start": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ + MarkdownDescription: "The start port of the service port range or exact port number if `end` is not set.", + }, + Resource: &schemaR.Int64Attribute{ + Required: true, + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + DataSource: &schemaD.Int64Attribute{ + Computed: true, + }, + }, + "end": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ + MarkdownDescription: "The end port of the service port range. If not specified, only the `start` value is used.", + Computed: true, + }, + Resource: &schemaR.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + }, + }, + }, + }, + } +} diff --git a/internal/provider/elb/virtual_service_schema_test.go b/internal/provider/elb/virtual_service_schema_test.go new file mode 100644 index 00000000..7ba2de0d --- /dev/null +++ b/internal/provider/elb/virtual_service_schema_test.go @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb_test + +import ( + "context" + "testing" + + // fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource". + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/elb" +) + +// Unit test for the schema of the resource cloudavenue_elb_virtual_service. +func TestVirtualServiceResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + elb.NewVirtualServiceResource().Schema(ctx, fwresource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} + +// Unit test for the schema of the datasource cloudavenue_elb_virtual_service. +func TestVirtualServiceDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + elb.NewVirtualServiceDataSource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/elb/virtual_service_types.go b/internal/provider/elb/virtual_service_types.go new file mode 100644 index 00000000..5fd630a9 --- /dev/null +++ b/internal/provider/elb/virtual_service_types.go @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package elb + +import ( + "context" + + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/edgeloadbalancer" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type ( + VirtualServiceModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + EdgeGatewayName supertypes.StringValue `tfsdk:"edge_gateway_name"` + EdgeGatewayID supertypes.StringValue `tfsdk:"edge_gateway_id"` + Description supertypes.StringValue `tfsdk:"description"` + Enabled supertypes.BoolValue `tfsdk:"enabled"` + PoolName supertypes.StringValue `tfsdk:"pool_name"` + PoolID supertypes.StringValue `tfsdk:"pool_id"` + ServiceEngineGroupName supertypes.StringValue `tfsdk:"service_engine_group_name"` + VirtualIP supertypes.StringValue `tfsdk:"virtual_ip"` + ServiceType supertypes.StringValue `tfsdk:"service_type"` + CertificateID supertypes.StringValue `tfsdk:"certificate_id"` + ServicePorts supertypes.ListNestedObjectValueOf[VirtualServiceModelServicePort] `tfsdk:"service_ports"` + } + + VirtualServiceModelServicePort struct { + Start supertypes.Int64Value `tfsdk:"start"` + End supertypes.Int64Value `tfsdk:"end"` + } +) + +func (rm *VirtualServiceModel) Copy() *VirtualServiceModel { + x := &VirtualServiceModel{} + utils.ModelCopy(rm, x) + return x +} + +// ToSDKVirtualServiceGroupModel converts the model to the SDK model. +func (rm *VirtualServiceModel) ToSDKVirtualServiceModelRequest(ctx context.Context, c edgeloadbalancer.Client) (*edgeloadbalancer.VirtualServiceModelRequest, diag.Diagnostics) { + var diags diag.Diagnostics + + poolID := rm.PoolID.Get() + if poolID == "" { + pool, err := c.GetPool(ctx, rm.EdgeGatewayID.Get(), rm.PoolName.Get()) + if err != nil { + diags.AddError("Error getting pool", err.Error()) + return nil, diags + } + + poolID = pool.ID + } + + vs := &edgeloadbalancer.VirtualServiceModelRequest{ + Name: rm.Name.Get(), + Description: rm.Description.Get(), + Enabled: rm.Enabled.GetPtr(), + ApplicationProfile: edgeloadbalancer.VirtualServiceModelApplicationProfile(rm.ServiceType.Get()), + VirtualIPAddress: rm.VirtualIP.Get(), + EdgeGatewayID: rm.EdgeGatewayID.Get(), + PoolID: poolID, + CertificateID: rm.CertificateID.GetPtr(), + ServicePorts: func() []edgeloadbalancer.VirtualServiceModelServicePort { + var ports []edgeloadbalancer.VirtualServiceModelServicePort + sps, d := rm.ServicePorts.Get(ctx) + if d.HasError() { + diags = append(diags, d...) + return nil + } + + for _, port := range sps { + ports = append(ports, edgeloadbalancer.VirtualServiceModelServicePort{ + Start: port.Start.GetIntPtr(), + End: port.End.GetIntPtr(), + }) + } + return ports + }(), + } + if rm.ServiceEngineGroupName.IsKnown() { + seg, err := c.GetVirtualService(ctx, rm.EdgeGatewayID.Get(), rm.ServiceEngineGroupName.Get()) + if err != nil { + diags.AddError("Error getting service engine group", err.Error()) + return nil, diags + } + + vs.ServiceEngineGroupID = &seg.ID + } + + return vs, diags +} diff --git a/internal/provider/org/certificate_library_datasource.go b/internal/provider/org/certificate_library_datasource.go new file mode 100644 index 00000000..f4cf3f1a --- /dev/null +++ b/internal/provider/org/certificate_library_datasource.go @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +// Package org provides a Terraform datasource. +package org + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/org" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +var ( + _ datasource.DataSource = &CertificateLibraryDatasource{} + _ datasource.DataSourceWithConfigure = &CertificateLibraryDatasource{} +) + +func NewCertificateLibraryDatasource() datasource.DataSource { + return &CertificateLibraryDatasource{} +} + +type CertificateLibraryDatasource struct { + client *client.CloudAvenue + orgClient org.Client +} + +// Init Initializes the data source. +func (d *CertificateLibraryDatasource) Init(ctx context.Context, dm *CertificateLibraryDatasourceModel) (diags diag.Diagnostics) { + var err error + + org, err := d.client.CAVSDK.V1.Org() + if err != nil { + diags.AddError("Error initializing ORG client", err.Error()) + } + + d.orgClient = org.Client + + return +} + +func (d *CertificateLibraryDatasource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_certificate_library" +} + +func (d *CertificateLibraryDatasource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = certificateLibrarySchema(ctx).GetDataSource(ctx) +} + +func (d *CertificateLibraryDatasource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *CertificateLibraryDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_org_certificate_library", d.client.GetOrgName(), metrics.Read)() + + config := &CertificateLibraryDatasourceModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + s := &CertificateLibraryResource{ + client: d.client, + orgClient: d.orgClient, + } + + configResource := &CertificateLibraryModel{ + ID: config.ID, + Name: config.Name, + } + + // Read data from the API + data, found, diags := s.read(ctx, configResource) + if !found { + resp.Diagnostics.AddError("Resource not found", fmt.Sprintf("The Certificate %s(%s) was not found", config.Name.Get(), config.ID.Get())) + return + } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + config.ID.Set(data.ID.Get()) + config.Name.Set(data.Name.Get()) + config.Description.Set(data.Description.Get()) + config.Certificate.Set(data.Certificate.Get()) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/provider/org/certificate_library_resource.go b/internal/provider/org/certificate_library_resource.go new file mode 100644 index 00000000..2d9a9892 --- /dev/null +++ b/internal/provider/org/certificate_library_resource.go @@ -0,0 +1,311 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package org + +import ( + "context" + "fmt" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/org" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &CertificateLibraryResource{} + _ resource.ResourceWithConfigure = &CertificateLibraryResource{} + _ resource.ResourceWithImportState = &CertificateLibraryResource{} +) + +// NewCertificateLibraryResource is a helper function to simplify the provider implementation. +func NewCertificateLibraryResource() resource.Resource { + return &CertificateLibraryResource{} +} + +// CertificateLibraryResource is the resource implementation. +type CertificateLibraryResource struct { + client *client.CloudAvenue + orgClient org.Client +} + +// Init Initializes the resource. +func (r *CertificateLibraryResource) Init(ctx context.Context, rm *CertificateLibraryModel) (diags diag.Diagnostics) { + var err error + + org, err := r.client.CAVSDK.V1.Org() + if err != nil { + diags.AddError("Error initializing ORG client", err.Error()) + } + + r.orgClient = org.Client + + return +} + +// Metadata returns the resource type name. +func (r *CertificateLibraryResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_certificate_library" +} + +// Schema defines the schema for the resource. +func (r *CertificateLibraryResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = certificateLibrarySchema(ctx).GetResource(ctx) +} + +func (r *CertificateLibraryResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +// Create creates the resource and sets the initial Terraform state. +func (r *CertificateLibraryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer metrics.New("cloudavenue_org_certificate_library", r.client.GetOrgName(), metrics.Create)() + + plan := &CertificateLibraryModel{} + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource creation logic here. + */ + + // Create the certificate library + newCertificate, err := r.orgClient.CreateCertificateInLibrary(ctx, &org.CertificateCreateRequest{ + Name: plan.Name.Get(), + Description: plan.Description.Get(), + Certificate: plan.Certificate.Get(), + PrivateKey: plan.PrivateKey.Get(), + Passphrase: plan.Passphrase.Get(), + }) + if err != nil { + resp.Diagnostics.AddError("error while creating certificate %s in library", err.Error()) + return + } + + plan.ID.Set(newCertificate.ID) + + // Use generic read function to refresh the state + state, found, d := r.read(ctx, plan) + if !found { + resp.Diagnostics.AddError("Resource not found", fmt.Sprintf("The certificate '%s' was not found after creation.", plan.Name.Get())) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *CertificateLibraryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer metrics.New("cloudavenue_org_certificate_library", r.client.GetOrgName(), metrics.Read)() + + state := &CertificateLibraryModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Refresh the state + stateRefreshed, found, d := r.read(ctx, state) + if !found { + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *CertificateLibraryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer metrics.New("cloudavenue_org_certificate_library", r.client.GetOrgName(), metrics.Update)() + + var ( + plan = &CertificateLibraryModel{} + state = &CertificateLibraryModel{} + ) + + // Get current plan and state + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource update here + */ + + _, err := r.orgClient.UpdateCertificateInLibrary(ctx, state.ID.Get(), &org.CertificateUpdateRequest{ + Name: plan.Name.Get(), + Description: plan.Description.Get(), + }) + if err != nil { + resp.Diagnostics.AddError("error while updating certificate : %s", err.Error()) + return + } + + // Use generic read function to refresh the state + stateRefreshed, _, d := r.read(ctx, plan) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *CertificateLibraryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer metrics.New("cloudavenue_org_certificate_library", r.client.GetOrgName(), metrics.Delete)() + + state := &CertificateLibraryModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource deletion here + */ + + if err := r.orgClient.DeleteCertificateFromLibrary(ctx, state.ID.Get()); err != nil { + resp.Diagnostics.AddError("error while deleting certificate : %s", err.Error()) + return + } +} + +func (r *CertificateLibraryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer metrics.New("cloudavenue_org_certificate_library", r.client.GetOrgName(), metrics.Import)() + + // * ID format is CertificateIdOrName + + config := &CertificateLibraryModel{} + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + if urn.IsCertificateLibraryItem(req.ID) { + config.ID.Set(req.ID) + } else { + config.Name.Set(req.ID) + } + + // Use generic read function to refresh the state + stateRefreshed, found, d := r.read(ctx, config) + if !found { + resp.Diagnostics.AddError("Resource not found", fmt.Sprintf("The certificate '%s' was not found after import.", req.ID)) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// * CustomFuncs + +// read is a generic read function that can be used by the resource Create, Read and Update functions. +func (r *CertificateLibraryResource) read(ctx context.Context, planOrState *CertificateLibraryModel) (stateRefreshed *CertificateLibraryModel, found bool, diags diag.Diagnostics) { + stateRefreshed = planOrState.Copy() + + var ( + certificate *org.CertificateModel + err error + ) + + if planOrState.ID.IsKnown() { + certificate, err = r.orgClient.GetCertificateFromLibrary(ctx, planOrState.ID.Get()) + } else { + certificate, err = r.orgClient.GetCertificateFromLibrary(ctx, planOrState.Name.Get()) + } + if err != nil { + if govcd.IsNotFound(err) { + return nil, false, diags + } + diags.AddError("error while fetching certificate library: %s", err.Error()) + return nil, false, diags + } + + // Set the refreshed state + stateRefreshed.ID.Set(certificate.ID) + stateRefreshed.Name.Set(certificate.Name) + stateRefreshed.Description.Set(certificate.Description) + stateRefreshed.Certificate.Set(certificate.Certificate) + + return stateRefreshed, true, nil +} diff --git a/internal/provider/org/certificate_library_schema.go b/internal/provider/org/certificate_library_schema.go new file mode 100644 index 00000000..8d880165 --- /dev/null +++ b/internal/provider/org/certificate_library_schema.go @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package org + +import ( + "context" + + superschema "github.com/orange-cloudavenue/terraform-plugin-framework-superschema" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + schemaR "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func certificateLibrarySchema(_ context.Context) superschema.Schema { + return superschema.Schema{ + Resource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_org_certificate_library` resource allows you to manage certificate in your organization's library.", + }, + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_org_certificate_library` data source allows you to retrieve information about an certificate in your organization's library.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the certificate library.", + }, + Resource: &schemaR.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name"), path.MatchRoot("id")), + }, + }, + }, + "name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the certificate library.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name"), path.MatchRoot("id")), + }, + }, + }, + "description": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The description of the certificate library.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "certificate": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The certificate content. It can be a PEM encoded certificate or a certificate chain.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + MarkdownDescription: "Contains all strings including the BEGIN CERTIFICATE and END CERTIFICATE lines. No empty lines are allowed.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "private_key": superschema.SuperStringAttribute{ + Resource: &schemaR.StringAttribute{ + MarkdownDescription: "The private key of the certificate in PEM format.", + Optional: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + "passphrase": superschema.SuperStringAttribute{ + Resource: &schemaR.StringAttribute{ + MarkdownDescription: "The passphrase of the private key.", + Optional: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, + } +} diff --git a/internal/provider/org/certificate_library_schema_test.go b/internal/provider/org/certificate_library_schema_test.go new file mode 100644 index 00000000..a5a75c30 --- /dev/null +++ b/internal/provider/org/certificate_library_schema_test.go @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package org_test + +import ( + "context" + "testing" + + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/org" +) + +// Unit test for the schema of the resource cloudavenue_org_certificate_library. +func TestCertificateLibraryResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + org.NewCertificateLibraryResource().Schema(ctx, fwresource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} + +// Unit test for the schema of the datasource cloudavenue_org_certificate_library + +func TestCertificateLibraryDatasourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + org.NewCertificateLibraryDatasource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/org/certificate_library_types.go b/internal/provider/org/certificate_library_types.go new file mode 100644 index 00000000..4ff90e0a --- /dev/null +++ b/internal/provider/org/certificate_library_types.go @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package org + +import ( + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1/org" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type CertificateLibraryModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + Description supertypes.StringValue `tfsdk:"description"` + Certificate supertypes.StringValue `tfsdk:"certificate"` + PrivateKey supertypes.StringValue `tfsdk:"private_key"` + Passphrase supertypes.StringValue `tfsdk:"passphrase"` +} + +type CertificateLibraryDatasourceModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + Description supertypes.StringValue `tfsdk:"description"` + Certificate supertypes.StringValue `tfsdk:"certificate"` +} + +// ToSDKCertificateLibraryModel converts the data Terraform to the SDK model. +func (rm *CertificateLibraryModel) ToSDKCertificateLibraryModel() org.CertificateModel { + return org.CertificateModel{ + ID: rm.ID.Get(), + Name: rm.Name.Get(), + Description: rm.Description.Get(), + Certificate: rm.Certificate.Get(), + } +} + +// Copy returns a copy of the CertificateLibraryModel. +func (rm *CertificateLibraryModel) Copy() *CertificateLibraryModel { + x := &CertificateLibraryModel{} + utils.ModelCopy(rm, x) + return x +} diff --git a/internal/provider/provider_datasources.go b/internal/provider/provider_datasources.go index a528c269..1cfc336e 100644 --- a/internal/provider/provider_datasources.go +++ b/internal/provider/provider_datasources.go @@ -14,11 +14,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/alb" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/backup" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/bms" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/catalog" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/edgegw" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/elb" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/iam" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/network" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/org" @@ -35,9 +35,6 @@ import ( // DataSources defines the data sources implemented in the provider. func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - // * ALB - alb.NewAlbPoolDataSource, - // * TIER0 vrf.NewTier0VrfsDataSource, vrf.NewTier0VrfDataSource, @@ -45,7 +42,7 @@ func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource // * PUBLICIP publicip.NewPublicIPDataSource, - // * EDGE GATEWAY + // * EdgeGateway edgegw.NewEdgeGatewayDataSource, edgegw.NewEdgeGatewaysDataSource, edgegw.NewFirewallDataSource, @@ -57,6 +54,12 @@ func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource edgegw.NewVPNIPSecDataSource, edgegw.NewAppPortProfileDataSource, + // * EdgeGateway LoadBalancer + elb.NewServiceEngineGroupDataSource, + elb.NewServiceEngineGroupsDataSource, + elb.NewPoolDataSource, + elb.NewVirtualServiceDataSource, + // * VDC vdc.NewVDCsDataSource, vdc.NewVDCDataSource, @@ -124,5 +127,6 @@ func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource // * ORG org.NewOrgDataSource, + org.NewCertificateLibraryDatasource, } } diff --git a/internal/provider/provider_resources.go b/internal/provider/provider_resources.go index 75c9fb47..731aef0d 100644 --- a/internal/provider/provider_resources.go +++ b/internal/provider/provider_resources.go @@ -14,10 +14,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/alb" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/backup" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/catalog" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/edgegw" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/elb" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/iam" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/network" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/org" @@ -33,10 +33,7 @@ import ( // Resources defines the resources implemented in the provider. func (p *cloudavenueProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ - // * ALB - alb.NewAlbPoolResource, - - // * EDGE GATEWAY + // * EdgeGateway edgegw.NewEdgeGatewayResource, edgegw.NewFirewallResource, edgegw.NewAppPortProfileResource, @@ -47,6 +44,10 @@ func (p *cloudavenueProvider) Resources(_ context.Context) []func() resource.Res edgegw.NewNATRuleResource, edgegw.NewVPNIPSecResource, + // * EdgeGateway LoadBalancer + elb.NewPoolResource, + elb.NewVirtualServiceResource, + // * VDC vdc.NewVDCResource, vdc.NewACLResource, @@ -114,5 +115,6 @@ func (p *cloudavenueProvider) Resources(_ context.Context) []func() resource.Res // * ORG org.NewOrgResource, + org.NewCertificateLibraryResource, } } diff --git a/internal/testsacc/acctest_datasources_test.go b/internal/testsacc/acctest_datasources_test.go index e3840762..05adf28c 100644 --- a/internal/testsacc/acctest_datasources_test.go +++ b/internal/testsacc/acctest_datasources_test.go @@ -13,6 +13,10 @@ import "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/he func GetDataSourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceConfig { return map[testsacc.ResourceName]func() *testsacc.ResourceConfig{ + // * ALB + ALBServiceEngineGroupDataSourceName: testsacc.NewResourceConfig(NewALBServiceEngineGroupDataSourceTest()), + ALBServiceEngineGroupsDataSourceName: testsacc.NewResourceConfig(NewALBServiceEngineGroupsDataSourceTest()), + // * Catalog CatalogDataSourceName: testsacc.NewResourceConfig(NewCatalogDataSourceTest()), CatalogACLDataSourceName: testsacc.NewResourceConfig(NewCatalogACLDataSourceTest()), @@ -49,6 +53,10 @@ func GetDataSourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceCo EdgeGatewayNATRuleDataSourceName: testsacc.NewResourceConfig(NewEdgeGatewayNATRuleDataSourceTest()), EdgeGatewayIPSetDataSourceName: testsacc.NewResourceConfig(NewEdgeGatewayIPSetDataSourceTest()), + // * EdgeGateway LoadBalancer (elb) + ELBPoolDataSourceName: testsacc.NewResourceConfig(NewELBPoolDataSourceTest()), + ELBVirtualServiceDataSourceName: testsacc.NewResourceConfig(NewELBVirtualServiceDataSourceTest()), + // * S3 S3BucketVersioningConfigurationDatasourceName: testsacc.NewResourceConfig(NewS3BucketVersioningConfigurationDatasourceTest()), S3BucketDatasourceName: testsacc.NewResourceConfig(NewS3BucketDatasourceTest()), @@ -69,5 +77,8 @@ func GetDataSourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceCo // * VApp VAppDatasourceName: testsacc.NewResourceConfig(NewVAppDatasourceTest()), VAppIsolatedNetworkDataSourceName: testsacc.NewResourceConfig(NewVAppIsolatedNetworkDataSourceTest()), + + // * Org + OrgCertificateLibraryDatasourceName: testsacc.NewResourceConfig(NewOrgCertificateLibraryDatasourceTest()), } } diff --git a/internal/testsacc/acctest_resources_test.go b/internal/testsacc/acctest_resources_test.go index 6429a825..13c0bdf2 100644 --- a/internal/testsacc/acctest_resources_test.go +++ b/internal/testsacc/acctest_resources_test.go @@ -49,6 +49,10 @@ func GetResourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceConf EdgeGatewayNATRuleResourceName: testsacc.NewResourceConfig(NewEdgeGatewayNATRuleResourceTest()), EdgeGatewayIPSetResourceName: testsacc.NewResourceConfig(NewEdgeGatewayIPSetResourceTest()), + // * EdgeGateway LoadBalancer (elb) + ELBPoolResourceName: testsacc.NewResourceConfig(NewELBPoolResourceTest()), + ELBVirtualServiceResourceName: testsacc.NewResourceConfig(NewELBVirtualServiceResourceTest()), + // * Backup BackupResourceName: testsacc.NewResourceConfig(NewBackupResourceTest()), @@ -76,6 +80,7 @@ func GetResourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceConf IAMUserSAMLResourceName: testsacc.NewResourceConfig(NewIAMUserSAMLResourceTest()), // * ORG - OrgResourceName: testsacc.NewResourceConfig(NewOrgResourceTest()), + OrgResourceName: testsacc.NewResourceConfig(NewOrgResourceTest()), + ORGCertificateLibraryResourceName: testsacc.NewResourceConfig(NewORGCertificateLibraryResourceTest()), } } diff --git a/internal/testsacc/alb_pool_datasource_test.go b/internal/testsacc/alb_pool_datasource_test.go deleted file mode 100644 index 51ba7c85..00000000 --- a/internal/testsacc/alb_pool_datasource_test.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -package testsacc - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" -) - -const testAccAlbPoolDataSourceConfig = ` -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = "tn01e02ocb0006205spt102" - name = "Example" - - persistence_profile = { - type = "CLIENT_IP" - } - - members = [ - { - ip_address = "192.168.1.1" - port = "80" - }, - { - ip_address = "192.168.1.2" - port = "80" - }, - { - ip_address = "192.168.1.3" - port = "80" - } - ] - - health_monitors = ["UDP", "TCP"] - } - -data "cloudavenue_alb_pool" "example" { - edge_gateway_name = cloudavenue_alb_pool.example.edge_gateway_name - name = cloudavenue_alb_pool.example.name -} -` - -func TestAccAlbPoolDataSource(t *testing.T) { - dataSourceName := "data.cloudavenue_alb_pool.example" - resourceName := "cloudavenue_alb_pool.example" - resource.Test(t, resource.TestCase{ - PreCheck: func() { TestAccPreCheck(t) }, - ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - // Apply test - Config: testAccAlbPoolDataSourceConfig, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerPool)), - resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), - resource.TestCheckResourceAttrPair(dataSourceName, "persistence_profile.#", resourceName, "persistence_profile.#"), - resource.TestCheckResourceAttrPair(dataSourceName, "members.#", resourceName, "members.#"), - resource.TestCheckResourceAttrPair(dataSourceName, "health_monitors.#", resourceName, "health_monitors.#"), - ), - }, - }, - }) -} diff --git a/internal/testsacc/alb_pool_resource_test.go b/internal/testsacc/alb_pool_resource_test.go deleted file mode 100644 index 536cf27b..00000000 --- a/internal/testsacc/alb_pool_resource_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Orange - * SPDX-License-Identifier: Mozilla Public License 2.0 - * - * This software is distributed under the MPL-2.0 license. - * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ - * or see the "LICENSE" file for more details. - */ - -package testsacc - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" -) - -const testAccAlbPoolResourceConfig = ` -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = "tn01e02ocb0006205spt102" - name = "Example" - - persistence_profile = { - type = "CLIENT_IP" - } - - members = [ - { - ip_address = "192.168.1.1" - port = "80" - }, - { - ip_address = "192.168.1.2" - port = "80" - }, - { - ip_address = "192.168.1.3" - port = "80" - } - ] - - health_monitors = ["UDP", "TCP"] - } -` - -const testAccAlbPoolResourceConfigUpdate = ` -resource "cloudavenue_alb_pool" "example" { - edge_gateway_name = "tn01e02ocb0006205spt102" - name = "Example" - } -` - -func TestAccAlbPoolResource(t *testing.T) { - const resourceName = "cloudavenue_alb_pool.example" - resource.Test(t, resource.TestCase{ - PreCheck: func() { TestAccPreCheck(t) }, - ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - // Apply test - Config: testAccAlbPoolResourceConfig, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerPool)), - resource.TestCheckResourceAttr(resourceName, "name", "Example"), - resource.TestCheckResourceAttr(resourceName, "persistence_profile.type", "CLIENT_IP"), - ), - }, - { - // Update test - Config: testAccAlbPoolResourceConfigUpdate, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerPool)), - resource.TestCheckResourceAttr(resourceName, "name", "Example"), - resource.TestCheckNoResourceAttr(resourceName, "persistence_profile"), - resource.TestCheckNoResourceAttr(resourceName, "members"), - resource.TestCheckNoResourceAttr(resourceName, "health_monitors"), - ), - }, - }, - }) -} diff --git a/internal/testsacc/edgegateway_lb_service_engine_group_datasource_test.go b/internal/testsacc/edgegateway_lb_service_engine_group_datasource_test.go new file mode 100644 index 00000000..b1477e33 --- /dev/null +++ b/internal/testsacc/edgegateway_lb_service_engine_group_datasource_test.go @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ALBServiceEngineGroupDataSource{} + +const ( + ALBServiceEngineGroupDataSourceName = testsacc.ResourceName("data.cloudavenue_elb_service_engine_group") +) + +type ALBServiceEngineGroupDataSource struct{} + +func NewALBServiceEngineGroupDataSourceTest() testsacc.TestACC { + return &ALBServiceEngineGroupDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *ALBServiceEngineGroupDataSource) GetResourceName() string { + return ALBServiceEngineGroupDataSourceName.String() +} + +func (r *ALBServiceEngineGroupDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetDataSourceConfig()[EdgeGatewayDataSourceName]().GetSpecificConfig("example_with_id")) + resp.Append(GetDataSourceConfig()[ALBServiceEngineGroupsDataSourceName]().GetDefaultConfig) + return +} + +func (r *ALBServiceEngineGroupDataSource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Test One (example) + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_service_engine_group" "example" { + name = data.cloudavenue_elb_service_engine_groups.example.service_engine_groups.0.name + edge_gateway_name = data.cloudavenue_edgegateway.example_with_id.name + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.ServiceEngineGroup)), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "max_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "reserved_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "deployed_virtual_services"), + }, + }, + } + }, + "example_with_id": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_service_engine_group" "example_with_id" { + id = data.cloudavenue_elb_service_engine_groups.example.service_engine_groups.0.id + edge_gateway_name = data.cloudavenue_edgegateway.example_with_id.name + }`, + // Here use resource config test to test the data source + // the field example is the name of the test + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.ServiceEngineGroup)), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "max_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "reserved_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "deployed_virtual_services"), + }, + }, + } + }, + "example_with_edge_id": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_service_engine_group" "example_with_edge_id" { + id = data.cloudavenue_elb_service_engine_groups.example.service_engine_groups.0.id + edge_gateway_id = data.cloudavenue_edgegateway.example_with_id.id + }`, + // Here use resource config test to test the data source + // the field example is the name of the test + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.ServiceEngineGroup)), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "max_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "reserved_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "deployed_virtual_services"), + }, + }, + } + }, + "example_with_name_and_edge_id": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_service_engine_group" "example_with_name_and_edge_id" { + name = data.cloudavenue_elb_service_engine_groups.example.service_engine_groups.0.name + edge_gateway_id = data.cloudavenue_edgegateway.example_with_id.id + }`, + // Here use resource config test to test the data source + // the field example is the name of the test + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.ServiceEngineGroup)), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "max_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "reserved_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "deployed_virtual_services"), + }, + }, + } + }, + } +} + +func TestAccALBServiceEngineGroupDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ALBServiceEngineGroupDataSource{}), + }) +} diff --git a/internal/testsacc/edgegateway_lb_service_engine_groups_datasource_test.go b/internal/testsacc/edgegateway_lb_service_engine_groups_datasource_test.go new file mode 100644 index 00000000..6932cc6a --- /dev/null +++ b/internal/testsacc/edgegateway_lb_service_engine_groups_datasource_test.go @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ALBServiceEngineGroupsDataSource{} + +const ( + ALBServiceEngineGroupsDataSourceName = testsacc.ResourceName("data.cloudavenue_elb_service_engine_groups") +) + +type ALBServiceEngineGroupsDataSource struct{} + +func NewALBServiceEngineGroupsDataSourceTest() testsacc.TestACC { + return &ALBServiceEngineGroupsDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *ALBServiceEngineGroupsDataSource) GetResourceName() string { + return ALBServiceEngineGroupsDataSourceName.String() +} + +func (r *ALBServiceEngineGroupsDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetDataSourceConfig()[EdgeGatewayDataSourceName]().GetSpecificConfig("example_with_id")) + return +} + +func (r *ALBServiceEngineGroupsDataSource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Test One (example) + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_service_engine_groups" "example" { + edge_gateway_name = data.cloudavenue_edgegateway.example_with_id.name + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.#"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.name"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.edge_gateway_id"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.max_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.reserved_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.deployed_virtual_services"), + }, + }, + } + }, + "example_with_id": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_service_engine_groups" "example_with_id" { + edge_gateway_id = data.cloudavenue_edgegateway.example_with_id.id + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.#"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.name"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.edge_gateway_id"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.max_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.reserved_virtual_services"), + resource.TestCheckResourceAttrSet(resourceName, "service_engine_groups.0.deployed_virtual_services"), + }, + }, + } + }, + } +} + +func TestAccALBServiceEngineGroupsDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ALBServiceEngineGroupsDataSource{}), + }) +} diff --git a/internal/testsacc/edgegw_edgegateway_datasource_test.go b/internal/testsacc/edgegw_edgegateway_datasource_test.go index 61c14840..754f2277 100644 --- a/internal/testsacc/edgegw_edgegateway_datasource_test.go +++ b/internal/testsacc/edgegw_edgegateway_datasource_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" ) @@ -37,7 +38,6 @@ func (r *EdgeGatewayDataSource) GetResourceName() string { } func (r *EdgeGatewayDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { - resp.Append(GetResourceConfig()[EdgeGatewayResourceName]().GetDefaultConfig) return } @@ -45,6 +45,10 @@ func (r *EdgeGatewayDataSource) Tests(ctx context.Context) map[testsacc.TestName return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ "example": func(_ context.Context, _ string) testsacc.Test { return testsacc.Test{ + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[EdgeGatewayResourceName]().GetDefaultConfig) + return + }, Create: testsacc.TFConfig{ TFConfig: ` data "cloudavenue_edgegateway" "example" { @@ -54,6 +58,32 @@ func (r *EdgeGatewayDataSource) Tests(ctx context.Context) map[testsacc.TestName }, } }, + "example_with_id": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_edgegateway" "example_with_id" { + id = cloudavenue_edgegateway.example.id + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.Gateway)), + }, + }, + } + }, + "example_for_elb": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_edgegateway" "example_for_elb" { + name = "tn01e02ocb0006205spt101" + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.Gateway)), + }, + }, + } + }, } } diff --git a/internal/testsacc/edgegw_ip_set_resource_test.go b/internal/testsacc/edgegw_ip_set_resource_test.go index ba071c20..92a0f95c 100644 --- a/internal/testsacc/edgegw_ip_set_resource_test.go +++ b/internal/testsacc/edgegw_ip_set_resource_test.go @@ -157,6 +157,28 @@ func (r *EdgeGatewayIPSetResource) Tests(ctx context.Context) map[testsacc.TestN }, } }, + "example_for_elb": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetDataSourceConfig()[EdgeGatewayDataSourceName]().GetSpecificConfig("example_for_elb")) + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_edgegateway_ip_set" "example_for_elb" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + ip_addresses = [ + "192.168.1.1", + "192.168.1.2", + "192.168.1.3", + ] + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + }`), + }, + } + }, } } diff --git a/internal/testsacc/elb_pool_datasource_test.go b/internal/testsacc/elb_pool_datasource_test.go new file mode 100644 index 00000000..3d4ddb93 --- /dev/null +++ b/internal/testsacc/elb_pool_datasource_test.go @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ELBPoolDataSource{} + +const ( + ELBPoolDataSourceName = testsacc.ResourceName("data.cloudavenue_elb_pool") +) + +type ELBPoolDataSource struct{} + +func NewELBPoolDataSourceTest() testsacc.TestACC { + return &ELBPoolDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *ELBPoolDataSource) GetResourceName() string { + return ELBPoolDataSourceName.String() +} + +func (r *ELBPoolDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + // Add dependencies config to the resource + resp.Append(GetResourceConfig()[ELBPoolResourceName]().GetDefaultConfig) + return +} + +func (r *ELBPoolDataSource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Test One (example) + "example": func(_ context.Context, _ string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_pool" "example" { + name = cloudavenue_elb_pool.example.name + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + }`, + // Here use resource config test to test the data source + // the field example is the name of the test + Checks: GetResourceConfig()[ELBPoolResourceName]().GetDefaultChecks(), + }, + } + }, + } +} + +func TestAccELBPoolDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ELBPoolDataSource{}), + }) +} diff --git a/internal/testsacc/elb_pool_resource_test.go b/internal/testsacc/elb_pool_resource_test.go new file mode 100644 index 00000000..46bce99d --- /dev/null +++ b/internal/testsacc/elb_pool_resource_test.go @@ -0,0 +1,515 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ELBPoolResource{} + +const ( + ELBPoolResourceName = testsacc.ResourceName("cloudavenue_elb_pool") +) + +type ELBPoolResource struct{} + +func NewELBPoolResourceTest() testsacc.TestACC { + return &ELBPoolResource{} +} + +// GetResourceName returns the name of the resource. +func (r *ELBPoolResource) GetResourceName() string { + return ELBPoolResourceName.String() +} + +func (r *ELBPoolResource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetDataSourceConfig()[EdgeGatewayDataSourceName]().GetSpecificConfig("example_for_elb")) + return +} + +func (r *ELBPoolResource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerPool)), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckResourceAttr(resourceName, "members.targets.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "members.targets.*", map[string]string{ + "ip_address": "192.168.0.1", + "port": "80", + // Default values + "ratio": "1", + "enabled": "true", + }), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CLIENT_IP"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Update name and add a new disabled target + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + }, + { + ip_address = "192.168.0.2" + port = 8080 + enabled = false + ratio = 2 + } + ] + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckResourceAttr(resourceName, "members.targets.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "members.targets.*", map[string]string{ + "ip_address": "192.168.0.1", + "port": "80", + // Default values + "ratio": "1", + "enabled": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "members.targets.*", map[string]string{ + "ip_address": "192.168.0.2", + "port": "8080", + "ratio": "2", + "enabled": "false", + }), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CLIENT_IP"), + }, + }, + // * Update remove the disabled target, change algorithm and add health monitors + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + algorithm = "ROUND_ROBIN" + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } + health = { + monitors = ["HTTP", "TCP"] + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "ROUND_ROBIN"), + resource.TestCheckResourceAttr(resourceName, "health.monitors.#", "2"), + resource.TestCheckResourceAttr(resourceName, "health.monitors.0", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "health.monitors.1", "TCP"), + resource.TestCheckResourceAttr(resourceName, "members.targets.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "members.targets.*", map[string]string{ + "ip_address": "192.168.0.1", + "port": "80", + // Default values + "ratio": "1", + "enabled": "true", + }), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CLIENT_IP"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + "example_with_edge_name": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerPool)), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example_with_edge_name" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_name = data.cloudavenue_edgegateway.example_for_elb.name + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckResourceAttr(resourceName, "members.targets.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "members.targets.*", map[string]string{ + "ip_address": "192.168.0.1", + "port": "80", + // Default values + "ratio": "1", + "enabled": "true", + }), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CLIENT_IP"), + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + "example_with_ip_set": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerPool)), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[EdgeGatewayIPSetResourceName]().GetSpecificConfig("example_for_elb")) + resp.Append(GetResourceConfig()[ORGCertificateLibraryResourceName]().GetDefaultConfig) + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example_with_ip_set" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + members = { + target_group = cloudavenue_edgegateway_ip_set.example_for_elb.id + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckNoResourceAttr(resourceName, "members.targets"), + resource.TestCheckResourceAttrWith(resourceName, "members.target_group", urn.TestIsType(urn.SecurityGroup)), + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CLIENT_IP"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Update add TLS + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example_with_ip_set" { + name = {{ get . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + members = { + target_group = cloudavenue_edgegateway_ip_set.example_for_elb.id + } + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckNoResourceAttr(resourceName, "members.targets"), + resource.TestCheckResourceAttrWith(resourceName, "members.target_group", urn.TestIsType(urn.SecurityGroup)), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.ca_certificate_refs.#", "1"), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CLIENT_IP"), + }, + }, + // * Update add persistence + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example_with_ip_set" { + name = {{ get . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + members = { + target_group = cloudavenue_edgegateway_ip_set.example_for_elb.id + } + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + } + persistence = { + type = "TLS" + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckNoResourceAttr(resourceName, "members.targets"), + resource.TestCheckResourceAttrWith(resourceName, "members.target_group", urn.TestIsType(urn.SecurityGroup)), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.ca_certificate_refs.#", "1"), + + resource.TestCheckResourceAttr(resourceName, "persistence.type", "TLS"), + resource.TestCheckNoResourceAttr(resourceName, "persistence.value"), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + }, + }, + // * Update change persistence + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_pool" "example_with_ip_set" { + name = {{ get . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + default_port = 80 + members = { + target_group = cloudavenue_edgegateway_ip_set.example_for_elb.id + } + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + } + persistence = { + type = "CUSTOM_HTTP_HEADER" + value = "X-Custom" + } + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "default_port", "80"), + resource.TestCheckNoResourceAttr(resourceName, "members.targets"), + resource.TestCheckResourceAttrWith(resourceName, "members.target_group", urn.TestIsType(urn.SecurityGroup)), + resource.TestCheckResourceAttr(resourceName, "tls.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "tls.common_name_check_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "tls.ca_certificate_refs.#", "1"), + + resource.TestCheckResourceAttr(resourceName, "persistence.type", "CUSTOM_HTTP_HEADER"), + resource.TestCheckResourceAttr(resourceName, "persistence.value", "X-Custom"), + + // Default values + resource.TestCheckResourceAttr(resourceName, "members.graceful_timeout_period", "1"), + resource.TestCheckResourceAttr(resourceName, "algorithm", "LEAST_CONNECTIONS"), + resource.TestCheckResourceAttr(resourceName, "health.passive_monitoring_enabled", "true"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + } +} + +func TestAccELBPoolResource(t *testing.T) { + cleanup := orgCertificateLibraryResourcePreCheck() + defer cleanup() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ELBPoolResource{}), + }) +} diff --git a/internal/testsacc/elb_virtual_service_datasource_test.go b/internal/testsacc/elb_virtual_service_datasource_test.go new file mode 100644 index 00000000..e5bb5f3a --- /dev/null +++ b/internal/testsacc/elb_virtual_service_datasource_test.go @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ELBVirtualServiceDataSource{} + +const ( + ELBVirtualServiceDataSourceName = testsacc.ResourceName("data.cloudavenue_elb_virtual_service") +) + +type ELBVirtualServiceDataSource struct{} + +func NewELBVirtualServiceDataSourceTest() testsacc.TestACC { + return &ELBVirtualServiceDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *ELBVirtualServiceDataSource) GetResourceName() string { + return ELBVirtualServiceDataSourceName.String() +} + +func (r *ELBVirtualServiceDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + // Add dependencies config to the resource + resp.Append(GetResourceConfig()[ELBVirtualServiceResourceName]().GetDefaultConfig) + return +} + +func (r *ELBVirtualServiceDataSource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Test One (example) + "example": func(_ context.Context, _ string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_elb_virtual_service" "example" { + name = cloudavenue_elb_virtual_service.example.name + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + }`, + // Here use resource config test to test the data source + // the field example is the name of the test + Checks: GetResourceConfig()[ELBVirtualServiceResourceName]().GetDefaultChecks(), + }, + } + }, + } +} + +func TestAccELBVirtualServiceDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ELBVirtualServiceDataSource{}), + }) +} diff --git a/internal/testsacc/elb_virtual_service_resource_test.go b/internal/testsacc/elb_virtual_service_resource_test.go new file mode 100644 index 00000000..ad99bf4f --- /dev/null +++ b/internal/testsacc/elb_virtual_service_resource_test.go @@ -0,0 +1,618 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ELBVirtualServiceResource{} + +const ( + ELBVirtualServiceResourceName = testsacc.ResourceName("cloudavenue_elb_virtual_service") +) + +type ELBVirtualServiceResource struct{} + +func NewELBVirtualServiceResourceTest() testsacc.TestACC { + return &ELBVirtualServiceResource{} +} + +// GetResourceName returns the name of the resource. +func (r *ELBVirtualServiceResource) GetResourceName() string { + return ELBVirtualServiceResourceName.String() +} + +func (r *ELBVirtualServiceResource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetDataSourceConfig()[EdgeGatewayDataSourceName]().GetSpecificConfig("example_for_elb")) + resp.Append(GetResourceConfig()[ELBPoolResourceName]().GetDefaultConfig) + return +} + +func (r *ELBVirtualServiceResource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Example with service_type: HTTP and Simple Ports + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerVirtualService)), + resource.TestCheckResourceAttrWith(resourceName, "pool_id", urn.TestIsType(urn.LoadBalancerPool)), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "pool_name"), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ generate . "virtual_ip" "private-ipv4" }} + service_type = "HTTP" + service_ports = [ + { + start = 80 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "80"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "80"), // port end = port start if not specified + resource.TestCheckNoResourceAttr(resourceName, "certificate_id"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Test error (bad service_type) + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example" { + name = {{ get . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ get . "virtual_ip" }} + service_type = "HTTE" + service_ports = [ + { + start = 80 + } + ] + }`), + TFAdvanced: testsacc.TFAdvanced{ + ExpectNonEmptyPlan: true, + PlanOnly: true, + ExpectError: regexp.MustCompile(`Attribute service_type value must be one of`), + }, + }, + // * Test Update (Add a port & change virtual_ip) + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example" { + name = {{ get . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ generate . "virtual_ip" "private-ipv4" }} + service_type = "HTTP" + service_ports = [ + { + start = 80 + }, + { + start = 8080 + end = 8090 + }, + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + resource.TestCheckResourceAttr(resourceName, "service_type", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "80"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "80"), // port end = port start if not specified + resource.TestCheckResourceAttr(resourceName, "service_ports.1.start", "8080"), + resource.TestCheckResourceAttr(resourceName, "service_ports.1.end", "8090"), + resource.TestCheckNoResourceAttr(resourceName, "certificate_id"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + // * Example with service_type: HTTPS / Certificate / PublicIP + "example_https": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerVirtualService)), + resource.TestCheckResourceAttrWith(resourceName, "pool_id", urn.TestIsType(urn.LoadBalancerPool)), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "pool_name"), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[ORGCertificateLibraryResourceName]().GetDefaultConfig) + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_https" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ generate . "virtual_ip" "public-ipv4" }} + service_type = "HTTPS" + certificate_id = cloudavenue_org_certificate_library.example.id + service_ports = [ + { + start = 443 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "HTTPS"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Update description and add a port + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_https" { + name = {{ get . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ get . "virtual_ip" }} + + service_type = "HTTPS" + certificate_id = cloudavenue_org_certificate_library.example.id + service_ports = [ + { + start = 443 + }, + { + start = 8443 + end = 8446 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "HTTPS"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckResourceAttr(resourceName, "service_ports.1.start", "8443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.1.end", "8446"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + Destroy: true, + } + }, + + "example_l_4_tls": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerVirtualService)), + resource.TestCheckResourceAttrWith(resourceName, "pool_id", urn.TestIsType(urn.LoadBalancerPool)), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "pool_name"), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[ORGCertificateLibraryResourceName]().GetDefaultConfig) + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_l_4_tls" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ generate . "virtual_ip" "public-ipv4" }} + service_type = "L4_TLS" + certificate_id = cloudavenue_org_certificate_library.example.id + service_ports = [ + { + start = 443 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "L4_TLS"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Update name and add a port + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_l_4_tls" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ get . "virtual_ip" }} + + service_type = "L4_TLS" + certificate_id = cloudavenue_org_certificate_library.example.id + service_ports = [ + { + start = 443 + }, + { + start = 8443 + end = 8446 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "L4_TLS"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckResourceAttr(resourceName, "service_ports.1.start", "8443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.1.end", "8446"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + "example_l_4_tcp": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerVirtualService)), + resource.TestCheckResourceAttrWith(resourceName, "pool_id", urn.TestIsType(urn.LoadBalancerPool)), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "pool_name"), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[ORGCertificateLibraryResourceName]().GetDefaultConfig) + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_l_4_tcp" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ generate . "virtual_ip" "public-ipv4" }} + service_type = "L4_TCP" + service_ports = [ + { + start = 443 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "L4_TCP"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckNoResourceAttr(resourceName, "certificate_id"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Update name and add a port + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_l_4_tcp" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ get . "virtual_ip" }} + + service_type = "L4_TCP" + service_ports = [ + { + start = 443 + }, + { + start = 8443 + end = 8446 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "L4_TCP"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckResourceAttr(resourceName, "service_ports.1.start", "8443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.1.end", "8446"), + resource.TestCheckNoResourceAttr(resourceName, "certificate_id"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + "example_l_4_udp": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.LoadBalancerVirtualService)), + resource.TestCheckResourceAttrWith(resourceName, "pool_id", urn.TestIsType(urn.LoadBalancerPool)), + resource.TestCheckResourceAttrWith(resourceName, "edge_gateway_id", urn.TestIsType(urn.Gateway)), + resource.TestCheckResourceAttrSet(resourceName, "edge_gateway_name"), + resource.TestCheckResourceAttrSet(resourceName, "pool_name"), + }, + CommonDependencies: func() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[ORGCertificateLibraryResourceName]().GetDefaultConfig) + return + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_l_4_udp" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ generate . "virtual_ip" "public-ipv4" }} + service_type = "L4_UDP" + service_ports = [ + { + start = 443 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "L4_UDP"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckNoResourceAttr(resourceName, "certificate_id"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // * Update name and add a port + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_elb_virtual_service" "example_l_4_udp" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + edge_gateway_id = data.cloudavenue_edgegateway.example_for_elb.id + enabled = true + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = {{ get . "virtual_ip" }} + + service_type = "L4_UDP" + service_ports = [ + { + start = 443 + }, + { + start = 8443 + end = 8446 + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "virtual_ip", testsacc.GetValueFromTemplate(resourceName, "virtual_ip")), + + resource.TestCheckResourceAttr(resourceName, "service_type", "L4_UDP"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.start", "443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.0.end", "443"), // port end = port start if not specified + resource.TestCheckResourceAttr(resourceName, "service_ports.1.start", "8443"), + resource.TestCheckResourceAttr(resourceName, "service_ports.1.end", "8446"), + resource.TestCheckNoResourceAttr(resourceName, "certificate_id"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"edge_gateway_id", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "id"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_id", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateIDBuilder: []string{"edge_gateway_name", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + } +} + +func TestAccELBVirtualServiceResource(t *testing.T) { + cleanup := orgCertificateLibraryResourcePreCheck() + defer cleanup() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ELBVirtualServiceResource{}), + }) +} diff --git a/internal/testsacc/org_certificate_library_datasource_test.go b/internal/testsacc/org_certificate_library_datasource_test.go new file mode 100644 index 00000000..8798fb6b --- /dev/null +++ b/internal/testsacc/org_certificate_library_datasource_test.go @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &OrgCertificateLibraryDatasource{} + +const ( + OrgCertificateLibraryDatasourceName = testsacc.ResourceName("data.cloudavenue_org_certificate_library") +) + +type OrgCertificateLibraryDatasource struct{} + +func NewOrgCertificateLibraryDatasourceTest() testsacc.TestACC { + return &OrgCertificateLibraryDatasource{} +} + +// GetResourceName returns the name of the resource. +func (r *OrgCertificateLibraryDatasource) GetResourceName() string { + return OrgCertificateLibraryDatasourceName.String() +} + +func (r *OrgCertificateLibraryDatasource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[ORGCertificateLibraryResourceName]().GetDefaultConfig) + return +} + +func (r *OrgCertificateLibraryDatasource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Test One (example) + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_org_certificate_library" "example" { + name = cloudavenue_org_certificate_library.example.name + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.CertificateLibraryItem)), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "certificate"), + }, + }, + } + }, + "example_id": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_org_certificate_library" "example_id" { + id = cloudavenue_org_certificate_library.example.id + }`, + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.CertificateLibraryItem)), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "certificate"), + }, + }, + } + }, + } +} + +func TestAccOrgCertificateLibraryDatasource(t *testing.T) { + cleanup := orgCertificateLibraryResourcePreCheck() + defer cleanup() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&OrgCertificateLibraryDatasource{}), + }) +} diff --git a/internal/testsacc/org_certificate_library_resource_test.go b/internal/testsacc/org_certificate_library_resource_test.go new file mode 100644 index 00000000..00f4d53d --- /dev/null +++ b/internal/testsacc/org_certificate_library_resource_test.go @@ -0,0 +1,151 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Orange + * SPDX-License-Identifier: Mozilla Public License 2.0 + * + * This software is distributed under the MPL-2.0 license. + * the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/ + * or see the "LICENSE" file for more details. + */ + +package testsacc + +import ( + "context" + "os" + "testing" + + "github.com/madflojo/testcerts" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &ORGCertificateLibraryResource{} + +const ( + ORGCertificateLibraryResourceName = testsacc.ResourceName("cloudavenue_org_certificate_library") +) + +type ORGCertificateLibraryResource struct{} + +func NewORGCertificateLibraryResourceTest() testsacc.TestACC { + return &ORGCertificateLibraryResource{} +} + +// GetResourceName returns the name of the resource. +func (r *ORGCertificateLibraryResource) GetResourceName() string { + return ORGCertificateLibraryResourceName.String() +} + +func (r *ORGCertificateLibraryResource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + return +} + +func (r *ORGCertificateLibraryResource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * First test named "example" + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.CertificateLibraryItem)), + resource.TestCheckResourceAttrSet(resourceName, "certificate"), + resource.TestCheckResourceAttrSet(resourceName, "private_key"), + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_org_certificate_library" "example" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + certificate = chomp(file("/tmp/cert.pem")) + private_key = chomp(file("/tmp/key.pem")) + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckNoResourceAttr(resourceName, "passphrase"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_org_certificate_library" "example" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + certificate = chomp(file("/tmp/cert.pem")) + private_key = chomp(file("/tmp/key.pem")) + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckNoResourceAttr(resourceName, "passphrase"), + }, + }, + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_org_certificate_library" "example" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + certificate = chomp(file("/tmp/cert.pem")) + private_key = chomp(file("/tmp/key.pem")) + passphrase = "password" + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttrSet(resourceName, "passphrase"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"id"}, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"passphrase", "private_key"}, + }, + { + ImportStateIDBuilder: []string{"name"}, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"passphrase", "private_key"}, + }, + }, + } + }, + } +} + +const ( + orgCertificateLibraryCertFile = "/tmp/cert.pem" + orgCertificateLibraryKeyFile = "/tmp/key.pem" +) + +func orgCertificateLibraryResourcePreCheck() (cleanup func()) { + if err := testcerts.GenerateCertsToFile( + orgCertificateLibraryCertFile, + orgCertificateLibraryKeyFile, + ); err != nil { + panic(err) + } + + return func() { + os.Remove(orgCertificateLibraryCertFile) + os.Remove(orgCertificateLibraryKeyFile) + } +} + +func TestAccORGCertificateLibraryResource(t *testing.T) { + cleanup := orgCertificateLibraryResourcePreCheck() + defer cleanup() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&ORGCertificateLibraryResource{}), + }) +} diff --git a/templates/resources/alb_pool.md.tmpl b/templates/data-sources/elb_pool.md.tmpl similarity index 90% rename from templates/resources/alb_pool.md.tmpl rename to templates/data-sources/elb_pool.md.tmpl index cb7a94b6..6812dc1a 100644 --- a/templates/resources/alb_pool.md.tmpl +++ b/templates/data-sources/elb_pool.md.tmpl @@ -1,6 +1,6 @@ --- page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" -subcategory: "ALB (Advanced Load Balancer)" +subcategory: "ELB (EdgeGateway Load Balancer)" description: |- {{ .Description | plainmarkdown | trimspace | prefixlines " " }} --- diff --git a/templates/data-sources/elb_service_engine_group.md.tmpl b/templates/data-sources/elb_service_engine_group.md.tmpl new file mode 100644 index 00000000..e09bf6b3 --- /dev/null +++ b/templates/data-sources/elb_service_engine_group.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/elb_service_engine_groups.md.tmpl b/templates/data-sources/elb_service_engine_groups.md.tmpl new file mode 100644 index 00000000..e09bf6b3 --- /dev/null +++ b/templates/data-sources/elb_service_engine_groups.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/elb_virtual_service.md.tmpl b/templates/data-sources/elb_virtual_service.md.tmpl new file mode 100644 index 00000000..6812dc1a --- /dev/null +++ b/templates/data-sources/elb_virtual_service.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/data-sources/org_certificate_library.md.tmpl b/templates/data-sources/org_certificate_library.md.tmpl new file mode 100644 index 00000000..179a4b55 --- /dev/null +++ b/templates/data-sources/org_certificate_library.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "Organization" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/resources/elb_pool.md.tmpl b/templates/resources/elb_pool.md.tmpl new file mode 100644 index 00000000..ad7e98bf --- /dev/null +++ b/templates/resources/elb_pool.md.tmpl @@ -0,0 +1,164 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + + ~> **SUBSCRIBE REQUIRED** This resource require to subscribe to the Load Balancer service. Please open a ticket to the support team to enable the service. + +{{ .Description | trimspace }} + +## Example Usage + +Basic working example: + +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } +} +``` + + -> More examples can be found at the [Advanced Usage](#advanced-usage) section. + + + +{{ .SchemaMarkdown | trimspace }} + +## Advanced Usage + +### Multiple Members and health monitors +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + algorithm = "ROUND_ROBIN" + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + }, + { + ip_address = "192.168.0.2" + port = 80 + } + ] + } + health = { + monitors = ["HTTP", "TCP"] + } +} +``` + +### Setting TLS configuration +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + members = { + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + } + ] + } + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + } +} +``` + +### Use IPSet for members +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + members = { + graceful_timeout_period = 2 + target_group = cloudavenue_edgegateway_ip_set.example.id + } +} +``` + +### Full configuration +```hcl +resource "cloudavenue_elb_pool" "example" { + name = "example" + edge_gateway_id = data.cloudavenue_edgegateway.example.id + enabled = true + default_port = 80 + algorithm = "ROUND_ROBIN" + members = { + graceful_timeout_period = 2 + targets = [ + { + ip_address = "192.168.0.1" + port = 80 + enabled = true + ratio = 1 + }, + { + ip_address = "192.168.0.2" + port = 80 + enabled = true + ratio = 1 + }, + { + ip_address = "192.168.0.10" + port = 8080 + enabled = true + ratio = 10 + } + ] + } + + health = { + monitors = ["HTTP", "TCP"] + passive_monitoring_enabled = true + } + + tls = { + enabled = true + ca_certificate_refs = [ + cloudavenue_org_certificate_library.example.id + ] + common_name_check_enabled = true + } + + persistence = { + type = "CUSTOM_HTTP_HEADER" + value = "X-Custom" + } +} +``` + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/elb_virtual_service.md.tmpl b/templates/resources/elb_virtual_service.md.tmpl new file mode 100644 index 00000000..dc0c8539 --- /dev/null +++ b/templates/resources/elb_virtual_service.md.tmpl @@ -0,0 +1,97 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "ELB (EdgeGateway Load Balancer)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + + ~> **SUBSCRIBE REQUIRED** This resource require to subscribe to the Load Balancer service. Please open a ticket to the support team to enable the service. + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + -> More examples can be found at the [Advanced Usage](#advanced-usage) section. + + +{{ .SchemaMarkdown | trimspace }} + +## Advanced Usage + +### Expose TLS service + +Use the resource `cloudavenue_org_certificate_library` to reference the certificate used for the HTTPS service. +This example exposes a HTTPS service on the virtual IP `192.168.0.1` on port `443`. + +```hcl +resource "cloudavenue_elb_virtual_service" "example_https" { + name = "example" + enabled = true + + edge_gateway_id = cloudavenue_edgegateway.example.id + pool_id = cloudavenue_elb_pool.example.id + certificate_id = cloudavenue_org_certificate_library.example.id + + virtual_ip = "192.168.0.1" + + service_type = "HTTPS" // Use HTTPS or L4_TLS + service_ports = [ + { + start = 443 + } + ] +} +``` + +### Expose L4 service + +This example exposes a L4 TCP service on the virtual IP `192.168.0.1` on port `443`. + +```hcl +resource "cloudavenue_elb_virtual_service" "example_https" { + name = "example" + enabled = true + + edge_gateway_id = cloudavenue_edgegateway.example.id + pool_id = cloudavenue_elb_pool.example.id + + virtual_ip = "192.168.0.1" + + service_type = "L4_TCP" // Use L4_TCP or L4_UDP + service_ports = [ + { + start = 443 + } + ] +} +``` + +### Expose service with public IP + +The ELB virtual service require a **dedicated public IP** to expose the service. +If another resource use the same public IP, the ELB virtual service will fail to create. + +```hcl +resource "cloudavenue_elb_virtual_service" "example_https" { + name = "example" + enabled = true + + edge_gateway_id = cloudavenue_edgegateway.example.id + pool_id = cloudavenue_elb_pool.example.id + virtual_ip = cloudavenue_public_ip.example.public_ip + + [...] +} +``` + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/data-sources/alb_pool.md.tmpl b/templates/resources/org_certificate_library.md.tmpl similarity index 90% rename from templates/data-sources/alb_pool.md.tmpl rename to templates/resources/org_certificate_library.md.tmpl index cb7a94b6..cec68c1d 100644 --- a/templates/data-sources/alb_pool.md.tmpl +++ b/templates/resources/org_certificate_library.md.tmpl @@ -1,6 +1,6 @@ --- page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" -subcategory: "ALB (Advanced Load Balancer)" +subcategory: "Organization" description: |- {{ .Description | plainmarkdown | trimspace | prefixlines " " }} ---