Skip to content
Merged
88 changes: 88 additions & 0 deletions .github/workflows/testacc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 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
Comment thread
fabiomatavelli marked this conversation as resolved.

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
Comment thread
yadvr marked this conversation as resolved.
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
Comment thread
vishesh92 marked this conversation as resolved.
- 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:
matrix:
cloudstack_version:
- 4.17.2.0
- 4.18.1.0
- 4.19.0.0
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,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:
Expand Down
119 changes: 117 additions & 2 deletions cloudstack/resource_cloudstack_service_offering.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
fabiomatavelli marked this conversation as resolved.
Outdated
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
},
},
},
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
85 changes: 85 additions & 0 deletions cloudstack/resource_cloudstack_service_offering_test.go
Original file line number Diff line number Diff line change
@@ -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
}
}
28 changes: 24 additions & 4 deletions website/docs/r/service_offering.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,36 @@ resource "cloudstack_service_offering" "example" {

The following arguments are supported:

* `name` - (Required) The name of the service offering.
* `display_text` - (Required) The display text of the service offering.
* `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.
* `name` - The name of the service offering.
* `display_text` - The display text of the service offering.

## Import

Expand Down