From 34a38010d4c0a324baee79a44702f06bfb2330bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Matavelli?= Date: Thu, 3 Aug 2023 19:01:33 +0100 Subject: [PATCH 1/9] chore: improve service offering resource --- .../resource_cloudstack_service_offering.go | 119 +++++++++++++++++- ...source_cloudstack_service_offering_test.go | 85 +++++++++++++ 2 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 cloudstack/resource_cloudstack_service_offering_test.go diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index ad887b13..4ecd08b2 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -42,6 +42,61 @@ func resourceCloudStackServiceOffering() *schema.Resource { Type: schema.TypeString, Required: true, }, + "cpu_number": { + Description: "Number of CPU cores", + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "cpu_speed": { + Description: "Speed of CPU in Mhz", + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "host_tags": { + Description: "The host tag for this service offering", + Type: schema.TypeString, + Optional: true, + }, + "limit_cpu_use": { + Description: "Restrict the CPU usage to committed service offering", + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + "memory": { + Description: "The total memory of the service offering in MB", + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "offer_ha": { + Description: "The HA for the service offering", + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + "storage_type": { + Description: "The storage type of the service offering. Values are local and shared", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "local", + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + v := val.(string) + + if v == "local" || v == "shared" { + return + } + + errs = append(errs, fmt.Errorf("storage type should be either local or shared, got %s", v)) + + return + }, + }, }, } } @@ -53,6 +108,33 @@ func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interf // Create a new parameter struct p := cs.ServiceOffering.NewCreateServiceOfferingParams(display_text, name) + if v, ok := d.GetOk("cpu_number"); ok { + p.SetCpunumber(v.(int)) + } + + if v, ok := d.GetOk("cpu_speed"); ok { + p.SetCpuspeed(v.(int)) + } + + if v, ok := d.GetOk("host_tags"); ok { + p.SetHosttags(v.(string)) + } + + if v, ok := d.GetOk("limit_cpu_use"); ok { + p.SetLimitcpuuse(v.(bool)) + } + + if v, ok := d.GetOk("memory"); ok { + p.SetMemory(v.(int)) + } + + if v, ok := d.GetOk("offer_ha"); ok { + p.SetOfferha(v.(bool)) + } + + if v, ok := d.GetOk("storage_type"); ok { + p.SetStoragetype(v.(string)) + } log.Printf("[DEBUG] Creating Service Offering %s", name) s, err := cs.ServiceOffering.CreateServiceOffering(p) @@ -84,8 +166,22 @@ func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interfac } d.SetId(s.Id) - d.Set("name", s.Name) - d.Set("display_text", s.Displaytext) + + fields := map[string]interface{}{ + "name": s.Name, + "display_text": s.Displaytext, + "cpu_number": s.Cpunumber, + "cpu_speed": s.Cpuspeed, + "host_tags": s.Hosttags, + "limit_cpu_use": s.Limitcpuuse, + "memory": s.Memory, + "offer_ha": s.Offerha, + "storage_type": s.Storagetype, + } + + for k, v := range fields { + d.Set(k, v) + } return nil } @@ -136,6 +232,25 @@ func resourceCloudStackServiceOfferingUpdate(d *schema.ResourceData, meta interf d.SetPartial("display_text") } + if d.HasChange("host_tags") { + log.Printf("[DEBUG] Host tags changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) + + // Set the new host tags + p.SetHosttags(d.Get("host_tags").(string)) + + // Update the host tags + _, err := cs.ServiceOffering.UpdateServiceOffering(p) + if err != nil { + return fmt.Errorf( + "Error updating the host tags for service offering %s: %s", name, err) + } + + d.SetPartial("host_tags") + } + d.Partial(false) return resourceCloudStackServiceOfferingRead(d, meta) diff --git a/cloudstack/resource_cloudstack_service_offering_test.go b/cloudstack/resource_cloudstack_service_offering_test.go new file mode 100644 index 00000000..1e1d6ccf --- /dev/null +++ b/cloudstack/resource_cloudstack_service_offering_test.go @@ -0,0 +1,85 @@ +// +// 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. +// + +package cloudstack + +import ( + "fmt" + "testing" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccCloudStackServiceOffering_basic(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOffering_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists("cloudstack_service_offering.test1", &so), + resource.TestCheckResourceAttr("cloudstack_service_offering.test1", "cpu_number", "2"), + resource.TestCheckResourceAttr("cloudstack_service_offering.test1", "cpu_speed", "2200"), + resource.TestCheckResourceAttr("cloudstack_service_offering.test1", "memory", "8096"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOffering_basic = ` +resource "cloudstack_service_offering" "test1" { + name = "service_offering_1" + display_text = "Test" + cpu_number = 2 + cpu_speed = 2200 + memory = 8096 +} +` + +func testAccCheckCloudStackServiceOfferingExists(n string, so *cloudstack.ServiceOffering) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No service offering ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + resp, _, err := cs.ServiceOffering.GetServiceOfferingByID(rs.Primary.ID) + if err != nil { + return err + } + + if resp.Id != rs.Primary.ID { + return fmt.Errorf("Service offering not found") + } + + *so = *resp + + return nil + } +} From e8a79e6ba7cdfad5404564584db9dd8eec624642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Matavelli?= Date: Thu, 3 Aug 2023 19:03:52 +0100 Subject: [PATCH 2/9] doc: update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 794ff18c..ca6db9f4 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ $ make test In order to run the full suite of Acceptance tests you will need to run the CloudStack Simulator. Please follow these steps to prepare an environment for running the Acceptance tests: ```sh -$ docker pull cloudstack/simulator -$ docker run --name simulator -p 8080:5050 -d cloudstack/simulator +$ docker pull apache/cloudstack-simulator +$ docker run --name cloudstack-simulator -p 8080:5050 -d apache/cloudstack-simulator ``` 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. @@ -74,7 +74,7 @@ When Docker started the container you can go to http://localhost:8080/client and Once the login page is shown and you can login, you need to provision a simulated data-center: ```sh -$ docker exec -ti cloudstack python /root/tools/marvin/marvin/deployDataCenter.py -i /root/setup/dev/advanced.cfg +docker exec -it cloudstack-simulator python /root/tools/marvin/marvin/deployDataCenter.py -i /root/setup/dev/advanced.cfg ``` If you refresh the client or login again, you will now get passed the initial welcome screen and be able to go to your account details and retrieve the API key and secret. Export those together with the URL: From 47af773b5792da17c9485d5e19cffe6fc3635047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Matavelli?= Date: Fri, 4 Aug 2023 09:48:22 +0100 Subject: [PATCH 3/9] ci: add acceptance test job --- .github/workflows/testacc.yml | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/testacc.yml diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml new file mode 100644 index 00000000..5ca9f5d7 --- /dev/null +++ b/.github/workflows/testacc.yml @@ -0,0 +1,63 @@ +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 + - name: Run acceptance test + run: | + make testacc + services: + cloudstack-simulator: + image: apache/cloudstack-simulator:${{ matrix.cloudstack_version }} + ports: + - 8080:5050 + strategy: + matrix: + cloudstack_version: + - 4.17.2.0 + - 4.18.0.0 + \ No newline at end of file From 0f3cf5acaa5eba2aa7597141479d6b28af1d6bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Matavelli?= Date: Fri, 4 Aug 2023 16:40:20 +0100 Subject: [PATCH 4/9] docs: add service_offering doc --- website/docs/r/service_offering.html.markdown | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 website/docs/r/service_offering.html.markdown diff --git a/website/docs/r/service_offering.html.markdown b/website/docs/r/service_offering.html.markdown new file mode 100644 index 00000000..c94ab460 --- /dev/null +++ b/website/docs/r/service_offering.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_service_offering" +sidebar_current: "docs-cloudstack-resource-service-offering" +description: |- + Creates a service offering. +--- + +# cloudstack_service_offering + +Creates a service offering. + +## Example Usage + +Basic usage: + +```hcl +resource "cloudstack_service_offering" "default" { + name = "Small" + cpu_number = 2 + cpu_speed = 1000 + memory = 4096 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name of the service offering. + Changing this forces a new resource to be created. + +* `display_text` - (Optional) The display text of the service offering. + +* `cpu_number` - (Optional) The number of CPU cores. + Changing this forces a new resource to be created. + +* `cpu_speed` - (Optional) The speed of the CPU in Mhz. + Changing this forces a new resource to be created. + +* `memory` - (Optional) Memory reserved by the VM in MB. + Changing this forces a new resource to be created. + +* `host_tags` - (Optional) The host tags for the service offering. + +* `limit_cpu_use` - (Optional) Restrict the CPU usage to committed service offering. + Changing this forces a new resource to be created. + +* `offer_ha` - (Optional) The HA for the service offering. + Changing this forces a new resource to be created. + +* `storage_type` - (Optional) The storage type of the service offering. Values are `local` and `shared`. + Changing this forces a new resource to be created. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the service offering. From 11119dc58c843b5f1583a95a5d8859552292dffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=CC=81bio=20Matavelli?= Date: Fri, 4 Aug 2023 17:25:15 +0100 Subject: [PATCH 5/9] ci: add missing license header --- .github/workflows/testacc.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml index 5ca9f5d7..f6e32fde 100644 --- a/.github/workflows/testacc.yml +++ b/.github/workflows/testacc.yml @@ -1,3 +1,20 @@ +# 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] @@ -5,7 +22,7 @@ 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 @@ -41,7 +58,7 @@ jobs: 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" @@ -60,4 +77,3 @@ jobs: cloudstack_version: - 4.17.2.0 - 4.18.0.0 - \ No newline at end of file From 8793d8b99983088a814d5e8eff1903a54276e232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=CC=81bio=20Matavelli?= Date: Mon, 4 Mar 2024 09:54:42 +0000 Subject: [PATCH 6/9] ci: create terraform project --- .github/workflows/testacc.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml index f6e32fde..563cc841 100644 --- a/.github/workflows/testacc.yml +++ b/.github/workflows/testacc.yml @@ -55,6 +55,15 @@ jobs: --data-urlencode 'password=password' \ --data-urlencode 'response=json' \ --data-urlencode 'domain=/' -j -c cookies.txt --output /dev/null + + # Create terraform project + curl -sf --location "${CLOUDSTACK_API_URL}" \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data-urlencode 'command=createProject' \ + --data-urlencode 'name=terraform' \ + --data-urlencode 'displaytext=terraform' \ + --data-urlencode 'response=json' -b cookies.txt -j --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') From 95253364cffee26bdf0d3c83f049c571b4380755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=CC=81bio=20Matavelli?= Date: Mon, 4 Mar 2024 09:55:34 +0000 Subject: [PATCH 7/9] ci: add more versions to test --- .github/workflows/testacc.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml index 563cc841..3153b116 100644 --- a/.github/workflows/testacc.yml +++ b/.github/workflows/testacc.yml @@ -85,4 +85,5 @@ jobs: matrix: cloudstack_version: - 4.17.2.0 - - 4.18.0.0 + - 4.18.1.0 + - 4.19.0.0 From 26812d7f1140f41b30f3db9810efaa1b31b27dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fa=CC=81bio=20Matavelli?= Date: Mon, 4 Mar 2024 10:45:20 +0000 Subject: [PATCH 8/9] ci: use cmk to create extra resource --- .github/workflows/testacc.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/testacc.yml b/.github/workflows/testacc.yml index 3153b116..fa364f81 100644 --- a/.github/workflows/testacc.yml +++ b/.github/workflows/testacc.yml @@ -56,14 +56,6 @@ jobs: --data-urlencode 'response=json' \ --data-urlencode 'domain=/' -j -c cookies.txt --output /dev/null - # Create terraform project - curl -sf --location "${CLOUDSTACK_API_URL}" \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --data-urlencode 'command=createProject' \ - --data-urlencode 'name=terraform' \ - --data-urlencode 'displaytext=terraform' \ - --data-urlencode 'response=json' -b cookies.txt -j --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') @@ -73,6 +65,13 @@ jobs: echo "CLOUDSTACK_API_KEY=$CLOUDSTACK_API_KEY" >> $GITHUB_ENV echo "CLOUDSTACK_SECRET_KEY=$CLOUDSTACK_SECRET_KEY" >> $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 From ba1f98f2e8435f8c482b0449406e0d913b08f338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Matavelli?= Date: Wed, 6 Mar 2024 09:39:04 +0000 Subject: [PATCH 9/9] Update cloudstack/resource_cloudstack_service_offering.go Co-authored-by: Vishesh --- cloudstack/resource_cloudstack_service_offering.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index 4ecd08b2..25dad334 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -84,7 +84,7 @@ func resourceCloudStackServiceOffering() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "local", + Default: "shared", ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { v := val.(string)