diff --git a/.asf.yaml b/.asf.yaml index 3c7ed7bc..c0623c86 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -10,6 +10,15 @@ github: issues: true projects: true + protected_branches: + main: + required_status_checks: + contexts: + - build + - all-jobs-passed + collaborators: - kiranchavala - vishesh92 + - fabiomatavelli + - CodeBleu diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml new file mode 100644 index 00000000..4915d5dd --- /dev/null +++ b/.github/workflows/acceptance.yml @@ -0,0 +1,130 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Acceptance Test + +on: + pull_request: + push: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-acceptance + cancel-in-progress: true + +permissions: + contents: read + +env: + CLOUDSTACK_API_URL: http://localhost:8080/client/api + CLOUDSTACK_VERSIONS: "['4.18.2.1', '4.19.0.2']" + +jobs: + prepare-matrix: + runs-on: ubuntu-latest + outputs: + cloudstack-versions: ${{ steps.set-versions.outputs.cloudstack-versions }} + steps: + - name: Set versions + id: set-versions + run: | + echo "cloudstack-versions=${{ env.CLOUDSTACK_VERSIONS }}" >> $GITHUB_OUTPUT + + acceptance-terraform: + name: Terraform ${{ matrix.terraform-version }} with Cloudstack ${{ matrix.cloudstack-version }} + needs: [prepare-matrix] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: Configure Cloudstack v${{ matrix.cloudstack-version }} + uses: ./.github/workflows/setup-cloudstack + id: setup-cloudstack + with: + cloudstack-version: ${{ matrix.cloudstack-version }} + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ matrix.terraform-version }} + terraform_wrapper: false + - name: Run acceptance test + env: + CLOUDSTACK_USER_ID: ${{ steps.setup-cloudstack.outputs.CLOUDSTACK_USER_ID }} + CLOUDSTACK_API_KEY: ${{ steps.setup-cloudstack.outputs.CLOUDSTACK_API_KEY }} + CLOUDSTACK_SECRET_KEY: ${{ steps.setup-cloudstack.outputs.CLOUDSTACK_SECRET_KEY }} + run: | + make testacc + services: + cloudstack-simulator: + image: apache/cloudstack-simulator:${{ matrix.cloudstack-version }} + ports: + - 8080:5050 + strategy: + fail-fast: false + matrix: + terraform-version: + - '1.8.*' + - '1.9.*' + cloudstack-version: ${{ fromJson(needs.prepare-matrix.outputs.cloudstack-versions) }} + + acceptance-opentofu: + name: OpenTofu ${{ matrix.opentofu-version }} with Cloudstack ${{ matrix.cloudstack-version }} + needs: [prepare-matrix] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: Configure Cloudstack v${{ matrix.cloudstack-version }} + uses: ./.github/workflows/setup-cloudstack + id: setup-cloudstack + with: + cloudstack-version: ${{ matrix.cloudstack-version }} + - uses: opentofu/setup-opentofu@v1 + with: + tofu_version: ${{ matrix.opentofu-version }} + - name: Run acceptance test + env: + CLOUDSTACK_USER_ID: ${{ steps.setup-cloudstack.outputs.CLOUDSTACK_USER_ID }} + CLOUDSTACK_API_KEY: ${{ steps.setup-cloudstack.outputs.CLOUDSTACK_API_KEY }} + CLOUDSTACK_SECRET_KEY: ${{ steps.setup-cloudstack.outputs.CLOUDSTACK_SECRET_KEY }} + run: | + make testacc + services: + cloudstack-simulator: + image: apache/cloudstack-simulator:${{ matrix.cloudstack-version }} + ports: + - 8080:5050 + strategy: + fail-fast: false + matrix: + opentofu-version: + - '1.6.*' + - '1.7.*' + cloudstack-version: ${{ fromJson(needs.prepare-matrix.outputs.cloudstack-versions) }} + + all-jobs-passed: # Will succeed if it is skipped + runs-on: ubuntu-latest + needs: [acceptance-terraform, acceptance-opentofu] + # Only run if any of the previous jobs failed + if: ${{ failure() }} + steps: + - name: Previous jobs failed + run: exit 1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2acf05b..6408ca3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,22 +22,24 @@ on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true - +permissions: + contents: read + jobs: build: runs-on: ubuntu-22.04 - + steps: - - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.19.x - - - name: Build - run: | - make build - - - name: Test - run: make test + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version-file: 'go.mod' + + - name: Build + run: | + make build + + - name: Test + run: make test diff --git a/.github/workflows/setup-cloudstack/action.yml b/.github/workflows/setup-cloudstack/action.yml new file mode 100644 index 00000000..dcbdbbe4 --- /dev/null +++ b/.github/workflows/setup-cloudstack/action.yml @@ -0,0 +1,83 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Setup Cloudstack + +inputs: + cloudstack-version: + description: 'Cloudstack version' + required: true +outputs: + CLOUDSTACK_USER_ID: + description: 'Cloudstack user id' + value: ${{ steps.setup-cloudstack.outputs.user_id }} + CLOUDSTACK_API_KEY: + description: 'Cloudstack api key' + value: ${{ steps.setup-cloudstack.outputs.api_key }} + CLOUDSTACK_SECRET_KEY: + description: 'Cloudstack secret key' + value: ${{ steps.setup-cloudstack.outputs.secret_key }} + CLOUDSTACK_API_URL: + description: 'Cloudstack API URL' + value: http://localhost:8080/client/api + +runs: + using: composite + steps: + - name: Wait Cloudstack to be ready + shell: bash + run: | + echo "Starting Cloudstack health check" + T=0 + until [ $T -gt 20 ] || curl -sfL http://localhost:8080 --output /dev/null + do + echo "Waiting for Cloudstack to be ready..." + ((T+=1)) + sleep 30 + done + - name: Setting up Cloudstack + id: setup-cloudstack + shell: bash + run: | + docker exec $(docker container ls --format=json -l | jq -r .ID) python /root/tools/marvin/marvin/deployDataCenter.py -i /root/setup/dev/advanced.cfg + curl -sf --location "${CLOUDSTACK_API_URL}" \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data-urlencode 'command=login' \ + --data-urlencode 'username=admin' \ + --data-urlencode 'password=password' \ + --data-urlencode 'response=json' \ + --data-urlencode 'domain=/' -j -c cookies.txt --output /dev/null + + CLOUDSTACK_USER_ID=$(curl -fs "${CLOUDSTACK_API_URL}?command=listUsers&response=json" -b cookies.txt | jq -r '.listusersresponse.user[0].id') + CLOUDSTACK_API_KEY=$(curl -s "${CLOUDSTACK_API_URL}?command=getUserKeys&id=${CLOUDSTACK_USER_ID}&response=json" -b cookies.txt | jq -r '.getuserkeysresponse.userkeys.apikey') + CLOUDSTACK_SECRET_KEY=$(curl -fs "${CLOUDSTACK_API_URL}?command=getUserKeys&id=${CLOUDSTACK_USER_ID}&response=json" -b cookies.txt | jq -r '.getuserkeysresponse.userkeys.secretkey') + + echo "::add-mask::$CLOUDSTACK_API_KEY" + echo "::add-mask::$CLOUDSTACK_SECRET_KEY" + + echo "user_id=$CLOUDSTACK_USER_ID" >> $GITHUB_OUTPUT + echo "api_key=$CLOUDSTACK_API_KEY" >> $GITHUB_OUTPUT + echo "secret_key=$CLOUDSTACK_SECRET_KEY" >> $GITHUB_OUTPUT + - name: Install CMK + shell: bash + run: | + curl -sfL https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.3.0/cmk.linux.x86-64 -o /usr/local/bin/cmk + chmod +x /usr/local/bin/cmk + - name: Create extra resources + shell: bash + run: | + cmk -u $CLOUDSTACK_API_URL -k $CLOUDSTACK_API_KEY -s $CLOUDSTACK_SECRET_KEY -o json create project name=terraform displaytext=terraform diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml deleted file mode 100644 index e2adc435..00000000 --- a/.github/workflows/testacc.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -name: Acceptance Test - -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-testacc - cancel-in-progress: true - -jobs: - testacc: - name: Acceptance Test - runs-on: ubuntu-22.04 - env: - CLOUDSTACK_API_URL: http://localhost:8080/client/api - steps: - - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.19.x - - name: Wait Cloudstack to be ready - run: | - echo "Starting Cloudstack health check" - T=0 - until [ $T -gt 20 ] || curl -sfL http://localhost:8080 --output /dev/null - do - echo "Waiting for Cloudstack to be ready..." - ((T+=1)) - sleep 30 - done - - name: Setting up Cloudstack - run: | - docker exec $(docker container ls --format=json -l | jq -r .ID) python /root/tools/marvin/marvin/deployDataCenter.py -i /root/setup/dev/advanced.cfg - curl -sf --location "${CLOUDSTACK_API_URL}" \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --data-urlencode 'command=login' \ - --data-urlencode 'username=admin' \ - --data-urlencode 'password=password' \ - --data-urlencode 'response=json' \ - --data-urlencode 'domain=/' -j -c cookies.txt --output /dev/null - - CLOUDSTACK_USER_ID=$(curl -fs "${CLOUDSTACK_API_URL}?command=listUsers&response=json" -b cookies.txt | jq -r '.listusersresponse.user[0].id') - CLOUDSTACK_API_KEY=$(curl -s "${CLOUDSTACK_API_URL}?command=getUserKeys&id=${CLOUDSTACK_USER_ID}&response=json" -b cookies.txt | jq -r '.getuserkeysresponse.userkeys.apikey') - CLOUDSTACK_SECRET_KEY=$(curl -fs "${CLOUDSTACK_API_URL}?command=getUserKeys&id=${CLOUDSTACK_USER_ID}&response=json" -b cookies.txt | jq -r '.getuserkeysresponse.userkeys.secretkey') - - echo "::add-mask::$CLOUDSTACK_API_KEY" - echo "::add-mask::$CLOUDSTACK_SECRET_KEY" - - echo "CLOUDSTACK_API_KEY=$CLOUDSTACK_API_KEY" >> $GITHUB_ENV - echo "CLOUDSTACK_SECRET_KEY=$CLOUDSTACK_SECRET_KEY" >> $GITHUB_ENV - echo "CLOUDSTACK_TEMPLATE_URL=http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-xen.vhd.bz2" >> $GITHUB_ENV - - name: Install CMK - run: | - curl -sfL https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.3.0/cmk.linux.x86-64 -o /usr/local/bin/cmk - chmod +x /usr/local/bin/cmk - - name: Create extra resources - run: | - cmk -u $CLOUDSTACK_API_URL -k $CLOUDSTACK_API_KEY -s $CLOUDSTACK_SECRET_KEY -o json create project name=terraform displaytext=terraform - - name: Run acceptance test - run: | - make testacc - services: - cloudstack-simulator: - image: apache/cloudstack-simulator:${{ matrix.cloudstack_version }} - ports: - - 8080:5050 - strategy: - fail-fast: false - matrix: - cloudstack_version: - - 4.17.2.0 - - 4.18.1.0 - - 4.19.0.0 diff --git a/README.md b/README.md index d7baa427..32dbae1c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ -CloudStack Terraform Provider -============================= +# CloudStack Terraform Provider -Requirements ------------- +## Requirements -- [Terraform](https://www.terraform.io/downloads.html) 1.0.x -- [Go](https://golang.org/doc/install) 1.20+ (to build the provider plugin) +- [Terraform](https://www.terraform.io/downloads.html) 1.0.x +- [Go](https://golang.org/doc/install) 1.20+ (to build the provider plugin) See wiki: https://github.com/apache/cloudstack-terraform-provider/wiki -Installing from Github Release ------------------------------- +## Installing from Github Release User can install the CloudStack Terraform Provider using the [Github Releases](https://github.com/apache/cloudstack-terraform-provider/releases) with the installation steps below. @@ -59,9 +56,10 @@ provider "cloudstack" { Note: this can be used when users are not able to install using the Terraform registry. -Installing from Terrafrom registry ----------------------------------- +## Installing from Terrafrom registry + To install the CloudStack provider, copy and paste the below code into your Terraform configuration. Then, run terraform init. + ```sh terraform { required_providers { @@ -79,15 +77,13 @@ provider "cloudstack" { User hitting installation issue using registry can install using the local install method. -Documentation -------------- +## Documentation For more details on how to use the provider, click [here](website/) or visit https://registry.terraform.io/providers/cloudstack/cloudstack/latest/docs -Developing the Provider ---------------------------- +## Developing the Provider -If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.16+ is *required*). You'll also need to correctly setup a [GOPATH](http://golang.org/doc/code.html#GOPATH), as well as adding `$GOPATH/bin` to your `$PATH`. +If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.16+ is _required_). You'll also need to correctly setup a [GOPATH](http://golang.org/doc/code.html#GOPATH), as well as adding `$GOPATH/bin` to your `$PATH`. Clone repository to: `$GOPATH/src/github.com/apache/cloudstack-terraform-provider` @@ -105,6 +101,7 @@ $ cd $GOPATH/src/github.com/apache/cloudstack-terraform-provider $ make build $ ls $GOPATH/bin/terraform-provider-cloudstack ``` + Once the build is ready, you have to copy the binary into Terraform locally (version appended). On Linux and Mac this path is at ~/.terraform.d/plugins, @@ -116,8 +113,7 @@ $ mkdir -p ~/.terraform.d/plugins/localdomain/provider/cloudstack/0.4.0/linux_a $ cp $GOPATH/bin/terraform-provider-cloudstack ~/.terraform.d/plugins/localdomain/provider/cloudstack/0.4.0/linux_amd64 ``` -Testing the Provider --------------------- +## Testing the Provider In order to test the provider, you can simply run `make test`. @@ -132,13 +128,13 @@ docker pull apache/cloudstack-simulator or pull it with a particular build tag -docker pull apache/cloudstack-simulator:4.17.2.0 +docker pull apache/cloudstack-simulator:4.19.0.0 docker run --name simulator -p 8080:5050 -d apache/cloudstack-simulator or -docker run --name simulator -p 8080:5050 -d apache/cloudstack-simulator:4.17.2.0 +docker run --name simulator -p 8080:5050 -d apache/cloudstack-simulator:4.19.0.0 ``` When Docker started the container you can go to http://localhost:8080/client and login to the CloudStack UI as user `admin` with password `password`. It can take a few minutes for the container is fully ready, so you probably need to wait and refresh the page for a few minutes before the login page is shown. @@ -163,8 +159,8 @@ In order for all the tests to pass, you will need to create a new (empty) projec $ make testacc ``` -Sample Terraform configuration when testing locally ------------------------------------------------------------- +## Sample Terraform configuration when testing locally + Below is an example configuration to initialize provider and create a Virtual Machine instance ```sh @@ -180,9 +176,9 @@ terraform { provider "cloudstack" { # Configuration options - api_url = "${var.cloudstack_api_url}" - api_key = "${var.cloudstack_api_key}" - secret_key = "${var.cloudstack_secret_key}" + api_url = var.cloudstack_api_url + api_key = var.cloudstack_api_key + secret_key = var.cloudstack_secret_key } resource "cloudstack_instance" "web" { @@ -193,6 +189,55 @@ resource "cloudstack_instance" "web" { zone = "2b61ed5d-e8bd-431d-bf52-d127655dffab" } ``` + +## Releasing Terraform Provider + +The CloudStack Terraform Provider release process requires `goreleaser` to be performed +by a committer or a PMC member of the project: https://goreleaser.com/install + +Check and ensure TF provider passes builds, GA and run this for local checks: +``` +goreleaser release --snapshot --clean +``` + +Next, create a personalised Github token:
 https://github.com/settings/tokens/new?scopes=repo,write:packages + +``` +export GITHUB_TOKEN="YOUR_GH_TOKEN" +``` + +Note: Due to how the Terraform registry works, it require the repo to be named in a certain way. +For this reason, the builds are published via https://github.com/cloudstack/terraform-provider-cloudstack/releases + +To do this, add the following remote for publishing builds: + +``` +git remote add cloudstack git@github.com:cloudstack/terraform-provider-cloudstack.git +``` + +Finally tag the release, for example and push to Github: + +``` +git tag -a v0.5.0-pre -m "v0.5.0-pre Alpha Release for testing purposes" +git push cloudstack v0.5.0-pre +``` + +Run goreleaser to release them: +``` +goreleaser release --clean +``` + +Or, just release using: +``` +goreleaser release +``` + +For testing or to push on other repos, you need to fix repo path in the +`.goreleaser.yml` and run: +``` +goreleaser release --clean --skip-validate +``` + ## History This codebase relicensed under APLv2 and donated to the Apache CloudStack diff --git a/cloudstack/data_source_cloudstack_common_schema.go b/cloudstack/data_source_cloudstack_common_schema.go index 73210e2d..516721f0 100644 --- a/cloudstack/data_source_cloudstack_common_schema.go +++ b/cloudstack/data_source_cloudstack_common_schema.go @@ -20,7 +20,7 @@ package cloudstack import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceFiltersSchema() *schema.Schema { diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index a78f39b1..5c909c8a 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackInstance() *schema.Resource { @@ -155,11 +155,7 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *cloudstack. d.Set("zone_id", instance.Zoneid) d.Set("nic", []interface{}{map[string]string{"ip_address": instance.Nic[0].Ipaddress}}) - tags := make(map[string]interface{}) - for _, tag := range instance.Tags { - tags[tag.Key] = tag.Value - } - d.Set("tags", tags) + d.Set("tags", tagsToMap(instance.Tags)) return nil } diff --git a/cloudstack/data_source_cloudstack_instance_test.go b/cloudstack/data_source_cloudstack_instance_test.go index 14616488..04ec4bf9 100644 --- a/cloudstack/data_source_cloudstack_instance_test.go +++ b/cloudstack/data_source_cloudstack_instance_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) // basic acceptance to check if the display_name attribute has same value in @@ -40,7 +40,6 @@ func TestAccInstanceDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "display_name", resourceName, "display_name"), ), - ExpectNonEmptyPlan: true, }, }, }) diff --git a/cloudstack/data_source_cloudstack_ipaddress.go b/cloudstack/data_source_cloudstack_ipaddress.go index ad7b1085..997a3a26 100644 --- a/cloudstack/data_source_cloudstack_ipaddress.go +++ b/cloudstack/data_source_cloudstack_ipaddress.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackIPAddress() *schema.Resource { @@ -124,7 +124,7 @@ func ipAddressDescriptionAttributes(d *schema.ResourceData, publicIpAddress *clo d.Set("project", publicIpAddress.Project) d.Set("ip_address", publicIpAddress.Ipaddress) d.Set("is_source_nat", publicIpAddress.Issourcenat) - d.Set("tags", publicIpAddress.Tags) + d.Set("tags", tagsToMap(publicIpAddress.Tags)) return nil } diff --git a/cloudstack/data_source_cloudstack_ipaddress_test.go b/cloudstack/data_source_cloudstack_ipaddress_test.go index 2e32b618..bb576bcf 100644 --- a/cloudstack/data_source_cloudstack_ipaddress_test.go +++ b/cloudstack/data_source_cloudstack_ipaddress_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccIPAddressDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccIPAddressDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "zone_name", resourceName, "zone"), ), - ExpectNonEmptyPlan: true, }, }, }) @@ -60,6 +59,6 @@ resource "cloudstack_ipaddress" "ipaddress-resource" { } output "ipaddress-output" { - value = "${data.cloudstack_ipaddress.ipaddress-data-source}" + value = data.cloudstack_ipaddress.ipaddress-data-source } ` diff --git a/cloudstack/data_source_cloudstack_network_offering.go b/cloudstack/data_source_cloudstack_network_offering.go index 8e57fae2..fff5f56c 100644 --- a/cloudstack/data_source_cloudstack_network_offering.go +++ b/cloudstack/data_source_cloudstack_network_offering.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackNetworkOffering() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_network_offering_test.go b/cloudstack/data_source_cloudstack_network_offering_test.go index 0101b318..d8d235dd 100644 --- a/cloudstack/data_source_cloudstack_network_offering_test.go +++ b/cloudstack/data_source_cloudstack_network_offering_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccNetworkOfferingDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccNetworkOfferingDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), ), - ExpectNonEmptyPlan: true, }, }, }) diff --git a/cloudstack/data_source_cloudstack_pod.go b/cloudstack/data_source_cloudstack_pod.go index d6d211ef..77a8ca70 100644 --- a/cloudstack/data_source_cloudstack_pod.go +++ b/cloudstack/data_source_cloudstack_pod.go @@ -27,7 +27,7 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackPod() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_pod_test.go b/cloudstack/data_source_cloudstack_pod_test.go index 815e57e5..36334ebc 100644 --- a/cloudstack/data_source_cloudstack_pod_test.go +++ b/cloudstack/data_source_cloudstack_pod_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccPodDataSource_basic(t *testing.T) { diff --git a/cloudstack/data_source_cloudstack_service_offering.go b/cloudstack/data_source_cloudstack_service_offering.go index 4b339032..8c1272a3 100644 --- a/cloudstack/data_source_cloudstack_service_offering.go +++ b/cloudstack/data_source_cloudstack_service_offering.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackServiceOffering() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_service_offering_test.go b/cloudstack/data_source_cloudstack_service_offering_test.go index d7455396..afe94d5e 100644 --- a/cloudstack/data_source_cloudstack_service_offering_test.go +++ b/cloudstack/data_source_cloudstack_service_offering_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccServiceOfferingDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccServiceOfferingDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), ), - ExpectNonEmptyPlan: true, }, }, }) diff --git a/cloudstack/data_source_cloudstack_ssh_keypair.go b/cloudstack/data_source_cloudstack_ssh_keypair.go index c7696cbb..1cb2734f 100644 --- a/cloudstack/data_source_cloudstack_ssh_keypair.go +++ b/cloudstack/data_source_cloudstack_ssh_keypair.go @@ -27,7 +27,7 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackSSHKeyPair() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_ssh_keypair_test.go b/cloudstack/data_source_cloudstack_ssh_keypair_test.go index 32bd3fd0..aa4bb97f 100644 --- a/cloudstack/data_source_cloudstack_ssh_keypair_test.go +++ b/cloudstack/data_source_cloudstack_ssh_keypair_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccSshKeyPairDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccSshKeyPairDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"), ), - ExpectNonEmptyPlan: true, }, }, }) diff --git a/cloudstack/data_source_cloudstack_template.go b/cloudstack/data_source_cloudstack_template.go index 0025f64b..5040f9bb 100644 --- a/cloudstack/data_source_cloudstack_template.go +++ b/cloudstack/data_source_cloudstack_template.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackTemplate() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_user.go b/cloudstack/data_source_cloudstack_user.go index a40862aa..23516f1c 100644 --- a/cloudstack/data_source_cloudstack_user.go +++ b/cloudstack/data_source_cloudstack_user.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackUser() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_user_test.go b/cloudstack/data_source_cloudstack_user_test.go index aa9c14d0..0e063b9f 100644 --- a/cloudstack/data_source_cloudstack_user_test.go +++ b/cloudstack/data_source_cloudstack_user_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccUserDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccUserDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "first_name", resourceName, "first_name"), ), - ExpectNonEmptyPlan: true, }, }, }) @@ -65,6 +64,6 @@ data "cloudstack_user" "user-data-source"{ } output "user-output" { - value = "${data.cloudstack_user.user-data-source}" + value = data.cloudstack_user.user-data-source } ` diff --git a/cloudstack/data_source_cloudstack_volume.go b/cloudstack/data_source_cloudstack_volume.go index e8c6bdb0..2e548eb0 100644 --- a/cloudstack/data_source_cloudstack_volume.go +++ b/cloudstack/data_source_cloudstack_volume.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackVolume() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_volume_test.go b/cloudstack/data_source_cloudstack_volume_test.go index 810ecdff..2c0cfac1 100644 --- a/cloudstack/data_source_cloudstack_volume_test.go +++ b/cloudstack/data_source_cloudstack_volume_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccVolumeDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccVolumeDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), ), - ExpectNonEmptyPlan: true, }, }, }) @@ -60,8 +59,8 @@ data "cloudstack_zone" "zone-data-source" { resource "cloudstack_volume" "volume-resource"{ name = "TestVolume" - disk_offering_id = "${cloudstack_disk_offering.disk-offering.id}" - zone_id = "${data.cloudstack_zone.zone-data-source.id}" + disk_offering_id = cloudstack_disk_offering.disk-offering.id + zone_id = data.cloudstack_zone.zone-data-source.id } data "cloudstack_volume" "volume-data-source"{ diff --git a/cloudstack/data_source_cloudstack_vpc.go b/cloudstack/data_source_cloudstack_vpc.go index a473d99c..287acf4e 100644 --- a/cloudstack/data_source_cloudstack_vpc.go +++ b/cloudstack/data_source_cloudstack_vpc.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackVPC() *schema.Resource { @@ -123,7 +123,7 @@ func vpcDescriptionAttributes(d *schema.ResourceData, vpc *cloudstack.VPC) error d.Set("network_domain", vpc.Networkdomain) d.Set("project", vpc.Project) d.Set("zone_name", vpc.Zonename) - d.Set("tags", vpc.Tags) + d.Set("tags", tagsToMap(vpc.Tags)) return nil } diff --git a/cloudstack/data_source_cloudstack_vpc_test.go b/cloudstack/data_source_cloudstack_vpc_test.go index 40d3f86d..2ac57dd1 100644 --- a/cloudstack/data_source_cloudstack_vpc_test.go +++ b/cloudstack/data_source_cloudstack_vpc_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccVPCDataSource_basic(t *testing.T) { @@ -67,6 +67,6 @@ data "cloudstack_vpc" "vpc-data-source"{ } output "vpc-output" { -value = "${data.cloudstack_vpc.vpc-data-source}" +value = data.cloudstack_vpc.vpc-data-source } ` diff --git a/cloudstack/data_source_cloudstack_vpn_connection.go b/cloudstack/data_source_cloudstack_vpn_connection.go index c0015bf6..90bbeedc 100644 --- a/cloudstack/data_source_cloudstack_vpn_connection.go +++ b/cloudstack/data_source_cloudstack_vpn_connection.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudstackVPNConnection() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_zone.go b/cloudstack/data_source_cloudstack_zone.go index 296c887c..9fb211b2 100644 --- a/cloudstack/data_source_cloudstack_zone.go +++ b/cloudstack/data_source_cloudstack_zone.go @@ -27,7 +27,7 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceCloudStackZone() *schema.Resource { diff --git a/cloudstack/data_source_cloudstack_zone_test.go b/cloudstack/data_source_cloudstack_zone_test.go index b02bb48b..c51948c2 100644 --- a/cloudstack/data_source_cloudstack_zone_test.go +++ b/cloudstack/data_source_cloudstack_zone_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccZoneDataSource_basic(t *testing.T) { @@ -38,7 +38,6 @@ func TestAccZoneDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), ), - ExpectNonEmptyPlan: true, }, }, }) diff --git a/cloudstack/metadata.go b/cloudstack/metadata.go index 59f68018..635e7666 100644 --- a/cloudstack/metadata.go +++ b/cloudstack/metadata.go @@ -23,7 +23,7 @@ import ( "log" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) // metadataSchema returns the schema to use for metadata diff --git a/cloudstack/provider.go b/cloudstack/provider.go index c35a263c..5aad7c55 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -23,12 +23,10 @@ import ( "errors" "github.com/go-ini/ini" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// Provider returns a terraform.ResourceProvider. -func Provider() terraform.ResourceProvider { +func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "api_url": { @@ -43,6 +41,7 @@ func Provider() terraform.ResourceProvider { Optional: true, DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_API_KEY", nil), ConflictsWith: []string{"config", "profile"}, + Sensitive: true, }, "secret_key": { @@ -50,6 +49,7 @@ func Provider() terraform.ResourceProvider { Optional: true, DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_SECRET_KEY", nil), ConflictsWith: []string{"config", "profile"}, + Sensitive: true, }, "config": { diff --git a/cloudstack/provider_test.go b/cloudstack/provider_test.go index d6726b77..fb868e4b 100644 --- a/cloudstack/provider_test.go +++ b/cloudstack/provider_test.go @@ -20,56 +20,119 @@ package cloudstack import ( + "context" "os" + "regexp" "testing" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -var testAccProviders map[string]terraform.ResourceProvider +var testAccProviders map[string]*schema.Provider var testAccProvider *schema.Provider +var testAccMuxProvider map[string]func() (tfprotov6.ProviderServer, error) + var cloudStackTemplateURL = os.Getenv("CLOUDSTACK_TEMPLATE_URL") func init() { - testAccProvider = Provider().(*schema.Provider) - testAccProviders = map[string]terraform.ResourceProvider{ + testAccProvider = Provider() + testAccProviders = map[string]*schema.Provider{ "cloudstack": testAccProvider, } + + testAccMuxProvider = map[string]func() (tfprotov6.ProviderServer, error){ + "cloudstack": func() (tfprotov6.ProviderServer, error) { + ctx := context.Background() + + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + Provider().GRPCProvider, + ) + + if err != nil { + return nil, err + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(New()), + func() tfprotov6.ProviderServer { + return upgradedSdkServer + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + } } func TestProvider(t *testing.T) { - if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + if err := Provider().InternalValidate(); err != nil { t.Fatalf("err: %s", err) } } func TestProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = Provider() + var _ *schema.Provider = Provider() } -func testSetValueOnResourceData(t *testing.T) { - d := schema.ResourceData{} - d.Set("id", "name") - - setValueOrID(&d, "id", "name", "54711781-274e-41b2-83c0-17194d0108f7") - - if d.Get("id").(string) != "name" { - t.Fatal("err: 'id' does not match 'name'") - } +func TestMuxServer(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccMuxProvider, + Steps: []resource.TestStep{ + { + Config: testMuxServerConfig_conflict, + ExpectError: regexp.MustCompile("Invalid Attribute Combination"), + }, + { + Config: testMuxServerConfig_basic, + }, + }, + }) } -func testSetIDOnResourceData(t *testing.T) { - d := schema.ResourceData{} - d.Set("id", "54711781-274e-41b2-83c0-17194d0108f7") +const testMuxServerConfig_basic = ` +resource "cloudstack_zone" "zone_resource"{ + name = "TestZone" + dns1 = "8.8.8.8" + internal_dns1 = "172.20.0.1" + network_type = "Advanced" + } - setValueOrID(&d, "id", "name", "54711781-274e-41b2-83c0-17194d0108f7") + data "cloudstack_zone" "zone_data_source"{ + filter{ + name = "name" + value = cloudstack_zone.zone_resource.name + } + } + ` - if d.Get("id").(string) != "54711781-274e-41b2-83c0-17194d0108f7" { - t.Fatal("err: 'id' does not match '54711781-274e-41b2-83c0-17194d0108f7'") - } +const testMuxServerConfig_conflict = ` +provider "cloudstack" { + api_url = "http://localhost:8080/client/api" + api_key = "xxxxx" + secret_key = "xxxxx" + config = "cloudstack.ini" +} + +data "cloudstack_zone" "zone_data_source"{ + filter{ + name = "name" + value = "test" + } } + ` func testAccPreCheck(t *testing.T) { if v := os.Getenv("CLOUDSTACK_API_URL"); v == "" { diff --git a/cloudstack/provider_v6.go b/cloudstack/provider_v6.go new file mode 100644 index 00000000..339e4b37 --- /dev/null +++ b/cloudstack/provider_v6.go @@ -0,0 +1,155 @@ +package cloudstack + +import ( + "context" + "fmt" + "os" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework-validators/providervalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type CloudstackProvider struct{} + +type CloudstackProviderModel struct { + ApiUrl types.String `tfsdk:"api_url"` + ApiKey types.String `tfsdk:"api_key"` + SecretKey types.String `tfsdk:"secret_key"` + Config types.String `tfsdk:"config"` + Profile types.String `tfsdk:"profile"` + HttpGetOnly types.Bool `tfsdk:"http_get_only"` + Timeout types.Int64 `tfsdk:"timeout"` +} + +var _ provider.Provider = (*CloudstackProvider)(nil) + +func New() provider.Provider { + return &CloudstackProvider{} +} + +func (p *CloudstackProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "cloudstack" +} + +func (p *CloudstackProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_url": schema.StringAttribute{ + Optional: true, + }, + "api_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "secret_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "config": schema.StringAttribute{ + Optional: true, + }, + "profile": schema.StringAttribute{ + Optional: true, + }, + "http_get_only": schema.BoolAttribute{ + Optional: true, + }, + "timeout": schema.Int64Attribute{ + Optional: true, + }, + }, + } +} + +func (p *CloudstackProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + apiUrl := os.Getenv("CLOUDSTACK_API_URL") + apiKey := os.Getenv("CLOUDSTACK_API_KEY") + secretKey := os.Getenv("CLOUDSTACK_SECRET_KEY") + httpGetOnly, _ := strconv.ParseBool(os.Getenv("CLOUDSTACK_HTTP_GET_ONLY")) + timeout, _ := strconv.ParseInt(os.Getenv("CLOUDSTACK_TIMEOUT"), 2, 64) + + var data CloudstackProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.ApiUrl.ValueString() != "" { + apiUrl = data.ApiUrl.ValueString() + } + + if data.ApiKey.ValueString() != "" { + apiKey = data.ApiKey.ValueString() + } + + if data.SecretKey.ValueString() != "" { + secretKey = data.SecretKey.ValueString() + } + + if data.HttpGetOnly.ValueBool() { + httpGetOnly = true + } + + if data.Timeout.ValueInt64() != 0 { + timeout = data.Timeout.ValueInt64() + } + + cfg := Config{ + APIURL: apiUrl, + APIKey: apiKey, + SecretKey: secretKey, + HTTPGETOnly: httpGetOnly, + Timeout: timeout, + } + + client, err := cfg.NewClient() + + if err != nil { + resp.Diagnostics.AddError("cloudstack", fmt.Sprintf("failed to create client: %T", err)) + return + } + + resp.ResourceData = client + resp.DataSourceData = client +} + +func (p *CloudstackProvider) ConfigValidators(ctx context.Context) []provider.ConfigValidator { + return []provider.ConfigValidator{ + providervalidator.Conflicting( + path.MatchRoot("api_url"), + path.MatchRoot("config"), + ), + providervalidator.Conflicting( + path.MatchRoot("api_url"), + path.MatchRoot("profile"), + ), + providervalidator.Conflicting( + path.MatchRoot("api_key"), + path.MatchRoot("config"), + ), + providervalidator.Conflicting( + path.MatchRoot("api_key"), + path.MatchRoot("profile"), + ), + providervalidator.Conflicting( + path.MatchRoot("secret_key"), + path.MatchRoot("config"), + ), + providervalidator.Conflicting( + path.MatchRoot("secret_key"), + path.MatchRoot("profile"), + ), + } +} + +func (p *CloudstackProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{} +} + +func (p *CloudstackProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} diff --git a/cloudstack/resource_cloudstack_account.go b/cloudstack/resource_cloudstack_account.go index a0d950f9..cc5d9c04 100644 --- a/cloudstack/resource_cloudstack_account.go +++ b/cloudstack/resource_cloudstack_account.go @@ -24,7 +24,7 @@ import ( "log" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackAccount() *schema.Resource { @@ -66,6 +66,10 @@ func resourceCloudStackAccount() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "domainid": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -80,6 +84,7 @@ func resourceCloudStackAccountCreate(d *schema.ResourceData, meta interface{}) e role_id := d.Get("role_id").(string) account_type := d.Get("account_type").(int) account := d.Get("account").(string) + domainid := d.Get("domainid").(string) // Create a new parameter struct p := cs.Account.NewCreateAccountParams(email, first_name, last_name, password, username) @@ -90,6 +95,7 @@ func resourceCloudStackAccountCreate(d *schema.ResourceData, meta interface{}) e } else { p.SetAccount(username) } + p.SetDomainid(domainid) log.Printf("[DEBUG] Creating Account %s", account) a, err := cs.Account.CreateAccount(p) diff --git a/cloudstack/resource_cloudstack_affinity_group.go b/cloudstack/resource_cloudstack_affinity_group.go index 7b64fbe9..bb473d34 100644 --- a/cloudstack/resource_cloudstack_affinity_group.go +++ b/cloudstack/resource_cloudstack_affinity_group.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackAffinityGroup() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_affinity_group_test.go b/cloudstack/resource_cloudstack_affinity_group_test.go index 9b1eb8b4..e4c8738e 100644 --- a/cloudstack/resource_cloudstack_affinity_group_test.go +++ b/cloudstack/resource_cloudstack_affinity_group_test.go @@ -24,8 +24,8 @@ import ( "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackAffinityGroup_basic(t *testing.T) { diff --git a/cloudstack/resource_cloudstack_attach_volume.go b/cloudstack/resource_cloudstack_attach_volume.go index 07892f27..5880c912 100644 --- a/cloudstack/resource_cloudstack_attach_volume.go +++ b/cloudstack/resource_cloudstack_attach_volume.go @@ -21,7 +21,7 @@ package cloudstack import ( "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackAttachVolume() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_attach_volume_test.go b/cloudstack/resource_cloudstack_attach_volume_test.go index cb79a34c..dcd3a780 100644 --- a/cloudstack/resource_cloudstack_attach_volume_test.go +++ b/cloudstack/resource_cloudstack_attach_volume_test.go @@ -22,7 +22,7 @@ package cloudstack import ( "testing" - "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccCloudstackAttachVolume_basic(t *testing.T) { @@ -43,6 +43,7 @@ func TestAccCloudstackAttachVolume_basic(t *testing.T) { const testAccCloudstackAttachVolume_basic = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -52,16 +53,16 @@ resource "cloudstack_network" "foo" { name = "terraform-test" display_name = "terraform" service_offering= "Small Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id template = "CentOS 5.6 (64-bit) no GUI (Simulator)" - zone = "${cloudstack_network.foo.zone}" + zone = cloudstack_network.foo.zone expunge = true } resource "cloudstack_disk" "foo" { name = "terraform-disk" disk_offering = "Small" - zone = "${cloudstack_instance.foobar.zone}" + zone = cloudstack_instance.foobar.zone } resource "cloudstack_attach_volume" "foo" { diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile.go b/cloudstack/resource_cloudstack_autoscale_vm_profile.go index f9d7cde4..04fc5d5f 100644 --- a/cloudstack/resource_cloudstack_autoscale_vm_profile.go +++ b/cloudstack/resource_cloudstack_autoscale_vm_profile.go @@ -22,12 +22,11 @@ package cloudstack import ( "fmt" "log" - "net/url" "strings" "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackAutoScaleVMProfile() *schema.Resource { @@ -101,16 +100,15 @@ func resourceCloudStackAutoScaleVMProfileCreate(d *schema.ResourceData, meta int if err != nil { return err } - p.SetDestroyvmgraceperiod(int(duration.Seconds())) + p.SetExpungevmgraceperiod(int(duration.Seconds())) } if v, ok := d.GetOk("other_deploy_params"); ok { - otherMap := v.(map[string]interface{}) - result := url.Values{} - for k, v := range otherMap { - result.Set(k, fmt.Sprint(v)) + nv := make(map[string]string) + for k, v := range v.(map[string]interface{}) { + nv[k] = v.(string) } - p.SetOtherdeployparams(result.Encode()) + p.SetOtherdeployparams(nv) } // Create the new vm profile @@ -164,19 +162,10 @@ func resourceCloudStackAutoScaleVMProfileRead(d *schema.ResourceData, meta inter setValueOrID(d, "template", template.Name, p.Templateid) setValueOrID(d, "zone", zone.Name, p.Zoneid) - d.Set("destroy_vm_grace_period", (time.Duration(p.Destroyvmgraceperiod) * time.Second).String()) + d.Set("destroy_vm_grace_period", (time.Duration(p.Expungevmgraceperiod) * time.Second).String()) - if p.Otherdeployparams != "" { - var values url.Values - values, err = url.ParseQuery(p.Otherdeployparams) - if err != nil { - return err - } - otherParams := make(map[string]interface{}, len(values)) - for key := range values { - otherParams[key] = values.Get(key) - } - d.Set("other_deploy_params", otherParams) + if p.Otherdeployparams != nil { + d.Set("other_deploy_params", p.Otherdeployparams) } metadata, err := getMetadata(cs, d, "AutoScaleVmProfile") @@ -211,7 +200,7 @@ func resourceCloudStackAutoScaleVMProfileUpdate(d *schema.ResourceData, meta int if err != nil { return err } - p.SetDestroyvmgraceperiod(int(duration.Seconds())) + p.SetExpungevmgraceperiod(int(duration.Seconds())) } _, err := cs.AutoScale.UpdateAutoScaleVmProfile(p) diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go b/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go index 06d60ac5..f417f45c 100644 --- a/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go +++ b/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go @@ -21,11 +21,12 @@ package cloudstack import ( "fmt" + "reflect" "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackAutoscaleVMProfile_basic(t *testing.T) { @@ -162,7 +163,10 @@ func testAccCheckCloudStackAutoscaleVMProfileBasicAttributes( return fmt.Errorf("Bad zone: %s", vmProfile.Zoneid) } - if vmProfile.Otherdeployparams != "displayname=display1&networkids=net1" { + if reflect.DeepEqual(vmProfile.Otherdeployparams, map[string]string{ + "displayname": "display1", + "networkids": "net1", + }) { return fmt.Errorf("Bad otherdeployparams: %s", vmProfile.Otherdeployparams) } @@ -174,8 +178,8 @@ func testAccCheckCloudStackAutoscaleVMProfileUpdatedAttributes( vmProfile *cloudstack.AutoScaleVmProfile) resource.TestCheckFunc { return func(s *terraform.State) error { - if vmProfile.Destroyvmgraceperiod != 10 { - return fmt.Errorf("Bad destroy_vm_grace_period: %d", vmProfile.Destroyvmgraceperiod) + if vmProfile.Expungevmgraceperiod != 10 { + return fmt.Errorf("Bad destroy_vm_grace_period: %d", vmProfile.Expungevmgraceperiod) } return nil diff --git a/cloudstack/resource_cloudstack_disk.go b/cloudstack/resource_cloudstack_disk.go index 24535835..dc325bca 100644 --- a/cloudstack/resource_cloudstack_disk.go +++ b/cloudstack/resource_cloudstack_disk.go @@ -24,7 +24,7 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackDisk() *schema.Resource { @@ -105,7 +105,6 @@ func resourceCloudStackDisk() *schema.Resource { func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - d.Partial(true) name := d.Get("name").(string) @@ -145,14 +144,6 @@ func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error creating the new disk %s: %s", name, err) } - d.SetPartial("name") - d.SetPartial("device_id") - d.SetPartial("disk_offering") - d.SetPartial("size") - d.SetPartial("virtual_machine_id") - d.SetPartial("project") - d.SetPartial("zone") - // Set the volume ID and partials d.SetId(r.Id) @@ -161,7 +152,6 @@ func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) erro if err != nil { return fmt.Errorf("Error setting tags on the new disk %s: %s", name, err) } - d.SetPartial("tags") if d.Get("attach").(bool) { if err := resourceCloudStackDiskAttach(d, meta); err != nil { @@ -169,10 +159,8 @@ func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) erro } // Set the additional partial - d.SetPartial("attach") } - d.Partial(false) return resourceCloudStackDiskRead(d, meta) } @@ -217,7 +205,6 @@ func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - d.Partial(true) name := d.Get("name").(string) @@ -257,8 +244,6 @@ func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) erro // Update the volume ID and set partials d.SetId(r.Id) - d.SetPartial("disk_offering") - d.SetPartial("size") } // If the device ID changed, just detach here so we can re-attach the @@ -278,9 +263,6 @@ func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) erro } // Set the additional partials - d.SetPartial("attach") - d.SetPartial("device_id") - d.SetPartial("virtual_machine_id") } else { // Detach the volume if err := resourceCloudStackDiskDetach(d, meta); err != nil { @@ -294,11 +276,8 @@ func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) erro if err != nil { return fmt.Errorf("Error updating tags on disk %s: %s", name, err) } - d.SetPartial("tags") } - d.Partial(false) - return resourceCloudStackDiskRead(d, meta) } diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index daa6b889..197eaf4b 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -23,7 +23,7 @@ import ( "log" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackDiskOffering() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_disk_test.go b/cloudstack/resource_cloudstack_disk_test.go index 3d182a12..76031a62 100644 --- a/cloudstack/resource_cloudstack_disk_test.go +++ b/cloudstack/resource_cloudstack_disk_test.go @@ -24,8 +24,8 @@ import ( "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackDisk_basic(t *testing.T) { @@ -228,6 +228,7 @@ resource "cloudstack_disk" "foo" { const testAccCloudStackDisk_deviceID = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -237,9 +238,9 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform" service_offering= "Small Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id template = "CentOS 5.6 (64-bit) no GUI (Simulator)" - zone = "${cloudstack_network.foo.zone}" + zone = cloudstack_network.foo.zone expunge = true } @@ -248,6 +249,6 @@ resource "cloudstack_disk" "foo" { attach = true device_id = 4 disk_offering = "Small" - virtual_machine_id = "${cloudstack_instance.foobar.id}" - zone = "${cloudstack_instance.foobar.zone}" + virtual_machine_id = cloudstack_instance.foobar.id + zone = cloudstack_instance.foobar.zone }` diff --git a/cloudstack/resource_cloudstack_domain.go b/cloudstack/resource_cloudstack_domain.go index 4123a6e6..3e11b1a7 100644 --- a/cloudstack/resource_cloudstack_domain.go +++ b/cloudstack/resource_cloudstack_domain.go @@ -24,7 +24,7 @@ import ( "log" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackDomain() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_egress_firewall.go b/cloudstack/resource_cloudstack_egress_firewall.go index 5994cdd0..9b7f3946 100644 --- a/cloudstack/resource_cloudstack_egress_firewall.go +++ b/cloudstack/resource_cloudstack_egress_firewall.go @@ -28,7 +28,7 @@ import ( "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackEgressFirewall() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_egress_firewall_test.go b/cloudstack/resource_cloudstack_egress_firewall_test.go index 6a5de880..28b664f7 100644 --- a/cloudstack/resource_cloudstack_egress_firewall_test.go +++ b/cloudstack/resource_cloudstack_egress_firewall_test.go @@ -25,8 +25,8 @@ import ( "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackEgressFirewall_basic(t *testing.T) { @@ -42,11 +42,11 @@ func TestAccCloudStackEgressFirewall_basic(t *testing.T) { resource.TestCheckResourceAttr( "cloudstack_egress_firewall.foo", "rule.#", "1"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.3342666485.cidr_list.140834516", "10.1.1.10/32"), + "cloudstack_egress_firewall.foo", "rule.0.cidr_list.0", "10.1.1.10/32"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.3342666485.protocol", "tcp"), + "cloudstack_egress_firewall.foo", "rule.0.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.3342666485.ports.32925333", "8080"), + "cloudstack_egress_firewall.foo", "rule.0.ports.0", "8080"), ), }, }, @@ -66,11 +66,11 @@ func TestAccCloudStackEgressFirewall_update(t *testing.T) { resource.TestCheckResourceAttr( "cloudstack_egress_firewall.foo", "rule.#", "1"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.3342666485.cidr_list.140834516", "10.1.1.10/32"), + "cloudstack_egress_firewall.foo", "rule.0.cidr_list.0", "10.1.1.10/32"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.3342666485.protocol", "tcp"), + "cloudstack_egress_firewall.foo", "rule.0.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.3342666485.ports.32925333", "8080"), + "cloudstack_egress_firewall.foo", "rule.0.ports.0", "8080"), ), }, @@ -81,19 +81,19 @@ func TestAccCloudStackEgressFirewall_update(t *testing.T) { resource.TestCheckResourceAttr( "cloudstack_egress_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.1558935996.cidr_list.140834516", "10.1.1.10/32"), + "cloudstack_egress_firewall.foo", "rule.0.cidr_list.0", "10.1.1.10/32"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.1558935996.cidr_list.2966983089", "10.1.1.11/32"), + "cloudstack_egress_firewall.foo", "rule.0.cidr_list.1", "10.1.1.11/32"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.1558935996.protocol", "tcp"), + "cloudstack_egress_firewall.foo", "rule.0.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.1558935996.ports.32925333", "8080"), + "cloudstack_egress_firewall.foo", "rule.0.ports.0", "8080"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.2961518528.cidr_list.140834516", "10.1.1.10/32"), + "cloudstack_egress_firewall.foo", "rule.1.cidr_list.0", "10.1.1.10/32"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.2961518528.protocol", "tcp"), + "cloudstack_egress_firewall.foo", "rule.1.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "rule.2961518528.ports.1889509032", "80"), + "cloudstack_egress_firewall.foo", "rule.1.ports.1", "80"), ), }, }, @@ -162,13 +162,14 @@ func testAccCheckCloudStackEgressFirewallDestroy(s *terraform.State) error { const testAccCloudStackEgressFirewall_basic = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" } resource "cloudstack_egress_firewall" "foo" { - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id rule { cidr_list = ["10.1.1.10/32"] @@ -180,13 +181,14 @@ resource "cloudstack_egress_firewall" "foo" { const testAccCloudStackEgressFirewall_update = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" } resource "cloudstack_egress_firewall" "foo" { - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id rule { cidr_list = ["10.1.1.10/32", "10.1.1.11/32"] diff --git a/cloudstack/resource_cloudstack_firewall.go b/cloudstack/resource_cloudstack_firewall.go index dbd922b6..b7ba7e0b 100644 --- a/cloudstack/resource_cloudstack_firewall.go +++ b/cloudstack/resource_cloudstack_firewall.go @@ -28,7 +28,7 @@ import ( "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackFirewall() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_firewall_test.go b/cloudstack/resource_cloudstack_firewall_test.go index 8fd66068..f43e3a0d 100644 --- a/cloudstack/resource_cloudstack_firewall_test.go +++ b/cloudstack/resource_cloudstack_firewall_test.go @@ -25,8 +25,8 @@ import ( "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackFirewall_basic(t *testing.T) { @@ -42,19 +42,19 @@ func TestAccCloudStackFirewall_basic(t *testing.T) { resource.TestCheckResourceAttr( "cloudstack_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.2263505090.cidr_list.3482919157", "10.0.0.0/24"), + "cloudstack_firewall.foo", "rule.1.cidr_list.0", "10.0.0.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.2263505090.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.1.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.2263505090.ports.32925333", "8080"), + "cloudstack_firewall.foo", "rule.1.ports.0", "8080"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.cidr_list.3482919157", "10.0.0.0/24"), + "cloudstack_firewall.foo", "rule.0.cidr_list.0", "10.0.0.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.0.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.ports.1209010669", "1000-2000"), + "cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.ports.1889509032", "80"), + "cloudstack_firewall.foo", "rule.0.ports.1", "80"), ), }, }, @@ -74,19 +74,19 @@ func TestAccCloudStackFirewall_update(t *testing.T) { resource.TestCheckResourceAttr( "cloudstack_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.2263505090.cidr_list.3482919157", "10.0.0.0/24"), + "cloudstack_firewall.foo", "rule.1.cidr_list.0", "10.0.0.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.2263505090.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.1.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.2263505090.ports.32925333", "8080"), + "cloudstack_firewall.foo", "rule.1.ports.0", "8080"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.cidr_list.3482919157", "10.0.0.0/24"), + "cloudstack_firewall.foo", "rule.0.cidr_list.0", "10.0.0.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.0.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.ports.1209010669", "1000-2000"), + "cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.ports.1889509032", "80"), + "cloudstack_firewall.foo", "rule.0.ports.1", "80"), ), }, @@ -97,29 +97,29 @@ func TestAccCloudStackFirewall_update(t *testing.T) { resource.TestCheckResourceAttr( "cloudstack_firewall.foo", "rule.#", "3"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3529885171.cidr_list.80081744", "10.0.1.0/24"), + "cloudstack_firewall.foo", "rule.0.cidr_list.1", "10.0.1.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3529885171.cidr_list.3482919157", "10.0.0.0/24"), + "cloudstack_firewall.foo", "rule.0.cidr_list.0", "10.0.0.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3529885171.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.0.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3529885171.ports.32925333", "8080"), + "cloudstack_firewall.foo", "rule.0.ports.0", "8080"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.cidr_list.3482919157", "10.0.0.0/24"), + "cloudstack_firewall.foo", "rule.1.cidr_list.0", "10.0.0.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.1.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.ports.1209010669", "1000-2000"), + "cloudstack_firewall.foo", "rule.1.ports.0", "1000-2000"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.3782201428.ports.1889509032", "80"), + "cloudstack_firewall.foo", "rule.1.ports.1", "80"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.4160426500.cidr_list.2835005819", "172.16.100.0/24"), + "cloudstack_firewall.foo", "rule.2.cidr_list.0", "172.16.100.0/24"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.4160426500.protocol", "tcp"), + "cloudstack_firewall.foo", "rule.2.protocol", "tcp"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.4160426500.ports.1889509032", "80"), + "cloudstack_firewall.foo", "rule.2.ports.1", "80"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "rule.4160426500.ports.3638101695", "443"), + "cloudstack_firewall.foo", "rule.2.ports.0", "443"), ), }, }, @@ -188,14 +188,15 @@ func testAccCheckCloudStackFirewallDestroy(s *terraform.State) error { const testAccCloudStackFirewall_basic = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" - source_nat_ip = true + source_nat_ip = true zone = "Sandbox-simulator" } resource "cloudstack_firewall" "foo" { - ip_address_id = "${cloudstack_network.foo.source_nat_ip_id}" + ip_address_id = cloudstack_network.foo.source_nat_ip_id rule { cidr_list = ["10.0.0.0/24"] @@ -213,14 +214,15 @@ resource "cloudstack_firewall" "foo" { const testAccCloudStackFirewall_update = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" - source_nat_ip = true + source_nat_ip = true zone = "Sandbox-simulator" } resource "cloudstack_firewall" "foo" { - ip_address_id = "${cloudstack_network.foo.source_nat_ip_id}" + ip_address_id = cloudstack_network.foo.source_nat_ip_id rule { cidr_list = ["10.0.0.0/24", "10.0.1.0/24"] diff --git a/cloudstack/resource_cloudstack_host.go b/cloudstack/resource_cloudstack_host.go index 46fc8e2b..3e88d728 100644 --- a/cloudstack/resource_cloudstack_host.go +++ b/cloudstack/resource_cloudstack_host.go @@ -28,7 +28,7 @@ import ( "time" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackHost() *schema.Resource { diff --git a/cloudstack/resource_cloudstack_host_test.go b/cloudstack/resource_cloudstack_host_test.go index 8da6f58c..da91b8ab 100644 --- a/cloudstack/resource_cloudstack_host_test.go +++ b/cloudstack/resource_cloudstack_host_test.go @@ -25,8 +25,8 @@ import ( "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackHost_basic(t *testing.T) { diff --git a/cloudstack/resource_cloudstack_instance.go b/cloudstack/resource_cloudstack_instance.go index bd69e838..fdf77ee9 100644 --- a/cloudstack/resource_cloudstack_instance.go +++ b/cloudstack/resource_cloudstack_instance.go @@ -28,7 +28,7 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceCloudStackInstance() *schema.Resource { @@ -139,9 +139,17 @@ func resourceCloudStackInstance() *schema.Resource { ForceNew: true, }, - "keypair": { - Type: schema.TypeString, - Optional: true, + "keypair": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"keypairs"}, + }, + + "keypairs": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ConflictsWith: []string{"keypair"}, }, "host_id": { @@ -213,6 +221,7 @@ func resourceCloudStackInstance() *schema.Resource { } func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) // Retrieve the service_offering ID @@ -354,6 +363,14 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) p.SetKeypair(keypair.(string)) } + if keypairs, ok := d.GetOk("keypairs"); ok { + var keypairStrings []string + for _, kp := range keypairs.([]interface{}) { + keypairStrings = append(keypairStrings, fmt.Sprintf("%v", kp)) + } + p.SetKeypairs(keypairStrings) + } + // If a host_id is supplied, add it to the parameter struct if hostid, ok := d.GetOk("host_id"); ok { @@ -373,7 +390,7 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) } if userData, ok := d.GetOk("user_data"); ok { - ud, err := getUserData(userData.(string), cs.HTTPGETOnly) + ud, err := getUserData(userData.(string)) if err != nil { return err } @@ -482,11 +499,7 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er d.Set("security_group_names", groups) } - tags := make(map[string]interface{}) - for _, tag := range vm.Tags { - tags[tag.Key] = tag.Value - } - d.Set("tags", tags) + d.Set("tags", tagsToMap(vm.Tags)) setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid) setValueOrID(d, "template", vm.Templatename, vm.Templateid) @@ -497,8 +510,8 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er } func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) - d.Partial(true) name := d.Get("name").(string) @@ -519,7 +532,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) "Error updating the display name for instance %s: %s", name, err) } - d.SetPartial("display_name") } // Check if the group is changed and if so, update the virtual machine @@ -539,12 +551,12 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) "Error updating the group for instance %s: %s", name, err) } - d.SetPartial("group") } // Attributes that require reboot to update if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") || - d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("user_data") { + d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("keypairs") || d.HasChange("user_data") { + // Before we can actually make these changes, the virtual machine must be stopped _, err := cs.VirtualMachine.StopVirtualMachine( cs.VirtualMachine.NewStopVirtualMachineParams(d.Id())) @@ -570,7 +582,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) "Error updating the name for instance %s: %s", name, err) } - d.SetPartial("name") } // Check if the service offering is changed and if so, update the offering @@ -592,7 +603,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf( "Error changing the service offering for instance %s: %s", name, err) } - d.SetPartial("service_offering") } // Check if the affinity group IDs have changed and if so, update the IDs @@ -615,7 +625,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf( "Error updating the affinity groups for instance %s: %s", name, err) } - d.SetPartial("affinity_group_ids") } // Check if the affinity group names have changed and if so, update the names @@ -638,14 +647,37 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf( "Error updating the affinity groups for instance %s: %s", name, err) } - d.SetPartial("affinity_group_names") } // Check if the keypair has changed and if so, update the keypair - if d.HasChange("keypair") { - log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name) + if d.HasChange("keypair") || d.HasChange("keypairs") { + log.Printf("[DEBUG] SSH keypair(s) changed for %s, starting update", name) - p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string)) + p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id()) + + if keypair, ok := d.GetOk("keypair"); ok { + p.SetKeypair(keypair.(string)) + } + + if keypairs, ok := d.GetOk("keypairs"); ok { + + // Convert keypairsInterface to []interface{} + keypairsInterfaces := keypairs.([]interface{}) + + // Now, safely convert []interface{} to []string with error handling + strKeyPairs := make([]string, len(keypairsInterfaces)) + + for i, v := range keypairsInterfaces { + switch v := v.(type) { + case string: + strKeyPairs[i] = v + default: + log.Printf("Value at index %d is not a string: %v", i, v) + continue + } + } + p.SetKeypairs(strKeyPairs) + } // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { @@ -655,16 +687,15 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) _, err = cs.SSH.ResetSSHKeyForVirtualMachine(p) if err != nil { return fmt.Errorf( - "Error changing the SSH keypair for instance %s: %s", name, err) + "Error changing the SSH keypair(s) for instance %s: %s", name, err) } - d.SetPartial("keypair") } // Check if the user data has changed and if so, update the user data if d.HasChange("user_data") { log.Printf("[DEBUG] user_data changed for %s, starting update", name) - ud, err := getUserData(d.Get("user_data").(string), cs.HTTPGETOnly) + ud, err := getUserData(d.Get("user_data").(string)) if err != nil { return err } @@ -676,7 +707,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf( "Error updating user_data for instance %s: %s", name, err) } - d.SetPartial("user_data") } // Start the virtual machine again @@ -693,7 +723,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) if err := updateTags(cs, d, "UserVm"); err != nil { return fmt.Errorf("Error updating tags on instance %s: %s", name, err) } - d.SetPartial("tags") } // Check if the details have changed and if so, update the details @@ -708,8 +737,6 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) p.SetDetails(vmDetails) } - d.Partial(false) - return resourceCloudStackInstanceRead(d, meta) } @@ -745,25 +772,11 @@ func resourceCloudStackInstanceImport(d *schema.ResourceData, meta interface{}) } // getUserData returns the user data as a base64 encoded string -func getUserData(userData string, httpGetOnly bool) (string, error) { +func getUserData(userData string) (string, error) { ud := userData if _, err := base64.StdEncoding.DecodeString(ud); err != nil { ud = base64.StdEncoding.EncodeToString([]byte(userData)) } - // deployVirtualMachine uses POST by default, so max userdata is 32K - maxUD := 32768 - - if httpGetOnly { - // deployVirtualMachine using GET instead, so max userdata is 2K - maxUD = 2048 - } - - if len(ud) > maxUD { - return "", fmt.Errorf( - "The supplied user_data contains %d bytes after encoding, "+ - "this exceeds the limit of %d bytes", len(ud), maxUD) - } - return ud, nil } diff --git a/cloudstack/resource_cloudstack_instance_test.go b/cloudstack/resource_cloudstack_instance_test.go index 86e8fbc2..5979aaaf 100644 --- a/cloudstack/resource_cloudstack_instance_test.go +++ b/cloudstack/resource_cloudstack_instance_test.go @@ -21,11 +21,12 @@ package cloudstack import ( "fmt" + "regexp" "testing" "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackInstance_basic(t *testing.T) { @@ -150,6 +151,68 @@ func TestAccCloudStackInstance_keyPair(t *testing.T) { }) } +func TestAccCloudStackInstance_keyPairs(t *testing.T) { + var instance cloudstack.VirtualMachine + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstance_keyPairs, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance), + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.#", "2"), // Expecting 2 key pairs + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.0", "terraform-test-keypair-foo"), + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.1", "terraform-test-keypair-bar"), + ), + }, + }, + }) +} + +func TestAccCloudStackInstance_keyPair_update(t *testing.T) { + var instance cloudstack.VirtualMachine + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstance_keyPair, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists( + "cloudstack_instance.foobar", &instance), + resource.TestCheckResourceAttr( + "cloudstack_instance.foobar", "keypair", "terraform-test-keypair"), + ), + }, + + { + Config: testAccCloudStackInstance_keyPairs, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance), + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.#", "2"), // Expecting 2 key pairs + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.0", "terraform-test-keypair-foo"), + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.1", "terraform-test-keypair-bar"), + ), + }, + + { + Config: testAccCloudStackInstance_keyPair, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists( + "cloudstack_instance.foobar", &instance), + resource.TestCheckResourceAttr( + "cloudstack_instance.foobar", "keypair", "terraform-test-keypair"), + ), + }, + }, + }) +} + func TestAccCloudStackInstance_project(t *testing.T) { var instance cloudstack.VirtualMachine @@ -212,6 +275,26 @@ func TestAccCloudStackInstance_importProject(t *testing.T) { }) } +func TestAccCloudStackInstance_userData(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceDestroy, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + VersionConstraint: ">= 3.6.0", + Source: "hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstance_userData, + ExpectError: regexp.MustCompile("User data has exceeded configurable max length"), + }, + }, + }) +} + func testAccCheckCloudStackInstanceExists( n string, instance *cloudstack.VirtualMachine) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -312,6 +395,7 @@ func testAccCheckCloudStackInstanceDestroy(s *terraform.State) error { const testAccCloudStackInstance_basic = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -321,7 +405,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "Small Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id template = "CentOS 5.6 (64-bit) no GUI (Simulator)" zone = "Sandbox-simulator" user_data = "foobar\nfoo\nbar" @@ -334,6 +418,7 @@ resource "cloudstack_instance" "foobar" { const testAccCloudStackInstance_stopped = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -343,7 +428,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "Small Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id template = "CentOS 5.6 (64-bit) no GUI (Simulator)" zone = "Sandbox-simulator" start_vm = false @@ -353,6 +438,7 @@ resource "cloudstack_instance" "foobar" { const testAccCloudStackInstance_renameAndResize = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -362,7 +448,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-updated" display_name = "terraform-updated" service_offering= "Medium Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id template = "CentOS 5.6 (64-bit) no GUI (Simulator)" zone = "Sandbox-simulator" user_data = "foobar\nfoo\nbar" @@ -372,6 +458,7 @@ resource "cloudstack_instance" "foobar" { const testAccCloudStackInstance_fixedIP = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -381,7 +468,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "Small Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id ip_address = "10.1.1.123" template = "CentOS 5.6 (64-bit) no GUI (Simulator)" zone = "Sandbox-simulator" @@ -391,6 +478,7 @@ resource "cloudstack_instance" "foobar" { const testAccCloudStackInstance_keyPair = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = "Sandbox-simulator" @@ -400,6 +488,33 @@ resource "cloudstack_ssh_keypair" "foo" { name = "terraform-test-keypair" } +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform-test" + service_offering= "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = "Sandbox-simulator" + keypair = cloudstack_ssh_keypair.foo.name + expunge = true +}` + +const testAccCloudStackInstance_keyPairs = ` +resource "cloudstack_network" "foo" { + name = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = "Sandbox-simulator" +} + +resource "cloudstack_ssh_keypair" "foo" { + name = "terraform-test-keypair-foo" +} + +resource "cloudstack_ssh_keypair" "bar" { + name = "terraform-test-keypair-bar" +} + resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" @@ -407,13 +522,14 @@ resource "cloudstack_instance" "foobar" { network_id = "${cloudstack_network.foo.id}" template = "CentOS 5.6 (64-bit) no GUI (Simulator)" zone = "Sandbox-simulator" - keypair = "${cloudstack_ssh_keypair.foo.name}" + keypairs = ["${cloudstack_ssh_keypair.foo.name}", "${cloudstack_ssh_keypair.bar.name}"] expunge = true }` const testAccCloudStackInstance_project = ` resource "cloudstack_network" "foo" { name = "terraform-network" + display_text = "terraform-network" cidr = "10.1.1.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" project = "terraform" @@ -424,9 +540,39 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "Small Instance" - network_id = "${cloudstack_network.foo.id}" + network_id = cloudstack_network.foo.id template = "CentOS 5.6 (64-bit) no GUI (Simulator)" project = "terraform" - zone = "${cloudstack_network.foo.zone}" + zone = cloudstack_network.foo.zone + expunge = true +}` + +const testAccCloudStackInstance_userData = ` +resource "random_bytes" "string" { + length = 32768 +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = "Sandbox-simulator" +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform-test" + service_offering= "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = cloudstack_network.foo.zone expunge = true + user_data = <<-EOFTF +#!/bin/bash + +echo < apache-cloudstack-terraform-provider-$version-src.tar.bz2.md5 - echo 'sha512' -gpg -v --print-md SHA512 apache-cloudstack-terraform-provider-$version-src.tar.bz2 > apache-cloudstack-terraform-provider-$version-src.tar.bz2.sha512 +sha512sum apache-cloudstack-terraform-provider-$version-src.tar.bz2 > apache-cloudstack-terraform-provider-$version-src.tar.bz2.sha512 echo 'verify' gpg -v --verify apache-cloudstack-terraform-provider-$version-src.tar.bz2.asc apache-cloudstack-terraform-provider-$version-src.tar.bz2 diff --git a/scripts/errcheck.sh b/scripts/errcheck.sh index 9c589f77..761a3774 100755 --- a/scripts/errcheck.sh +++ b/scripts/errcheck.sh @@ -28,7 +28,7 @@ if ! which errcheck > /dev/null; then fi err_files=$(errcheck -ignoretests \ - -ignore 'github.com/hashicorp/terraform/helper/schema:Set' \ + -ignore 'github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema:Set' \ -ignore 'bytes:.*' \ -ignore 'io:Close|Write' \ $(go list ./...| grep -v /vendor/)) diff --git a/website/docs/r/account.html.markdown b/website/docs/r/account.html.markdown index 5d555a38..53b1f755 100644 --- a/website/docs/r/account.html.markdown +++ b/website/docs/r/account.html.markdown @@ -36,6 +36,7 @@ The following arguments are supported: * `account_type` - (Required) The account type. Possible values are `0` for regular user, `1` for admin, and `2` for domain admin. * `role_id` - (Required) The ID of the role associated with the account. * `account` - (Optional) The account name. If not specified, the username will be used as the account name. +* `domainid` - (Optional) Creates the user under the specified domain ## Attributes Reference diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index ee21f692..d4d61872 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -34,7 +34,7 @@ The following arguments are supported: * `service_offering` - (Required) The name or ID of the service offering used for this instance. -* `host_id` - (Optional) destination Host ID to deploy the VM to - parameter available +* `host_id` - (Optional) destination Host ID to deploy the VM to - parameter available for root admin only * `pod_id` - (Optional) destination Pod ID to deploy the VM to - parameter available for root admin only @@ -82,7 +82,10 @@ The following arguments are supported: instance. This can be either plain text or base64 encoded text. * `keypair` - (Optional) The name of the SSH key pair that will be used to - access this instance. + access this instance. (Mutual exclusive with keypairs) + +* `keypairs` - (Optional) A list of SSH key pair names that will be used to + access this instance. (Mutual exclusive with keypair) * `expunge` - (Optional) This determines if the instance is expunged when it is destroyed (defaults false)