Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions cloudstack/data_source_cloudstack_security_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//
// 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 (
"encoding/json"
"fmt"
"log"
"regexp"
"strings"

"github.com/apache/cloudstack-go/v2/cloudstack"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceCloudstackSecurityGroup() *schema.Resource {
return &schema.Resource{
Read: dataSourceCloudstackSecurityGroupRead,
Schema: map[string]*schema.Schema{
"filter": dataSourceFiltersSchema(),

//Computed values
"name": {
Type: schema.TypeString,
Computed: true,
},
"description": {
Type: schema.TypeString,
Computed: true,
},
"project": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
},
}
}

func dataSourceCloudstackSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
p := cs.SecurityGroup.NewListSecurityGroupsParams()

// If there is a project supplied, we retrieve and set the project id
if err := setProjectid(p, cs, d); err != nil {
return err
}

securityGroups, err := cs.SecurityGroup.ListSecurityGroups(p)
if err != nil {
return fmt.Errorf("failed to list security groups: %s", err)
}

filters := d.Get("filter").(*schema.Set)
var matches []*cloudstack.SecurityGroup

for _, securityGroup := range securityGroups.SecurityGroups {
match, err := applySecurityGroupFilters(securityGroup, filters)
if err != nil {
return err
}
if match {
matches = append(matches, securityGroup)
}
}

if len(matches) == 0 {
return fmt.Errorf("no security group matches the specified filters")
}
if len(matches) > 1 {
return fmt.Errorf("multiple security groups match the specified filters")
}

securityGroup := matches[0]
log.Printf("[DEBUG] Selected security group: %s", securityGroup.Name)

return securityGroupDescriptionAttributes(d, securityGroup)
}

func securityGroupDescriptionAttributes(d *schema.ResourceData, securityGroup *cloudstack.SecurityGroup) error {
d.SetId(securityGroup.Id)

if err := d.Set("name", securityGroup.Name); err != nil {
return fmt.Errorf("failed to set security group name: %s", err)
}
if err := d.Set("description", securityGroup.Description); err != nil {
return fmt.Errorf("failed to set security group description: %s", err)
}

setValueOrID(d, "project", securityGroup.Project, securityGroup.Projectid)

return nil
}

func applySecurityGroupFilters(securityGroup *cloudstack.SecurityGroup, filters *schema.Set) (bool, error) {
securityGroupJSON, err := json.Marshal(securityGroup)
if err != nil {
return false, fmt.Errorf("failed to encode security group: %s", err)
}

var fields map[string]interface{}
if err := json.Unmarshal(securityGroupJSON, &fields); err != nil {
return false, fmt.Errorf("failed to decode security group: %s", err)
}

for _, filter := range filters.List() {
values := filter.(map[string]interface{})
pattern, err := regexp.Compile(values["value"].(string))
if err != nil {
return false, fmt.Errorf("invalid regex: %s", err)
}

name := strings.ReplaceAll(values["name"].(string), "_", "")
value, ok := fields[name]
if !ok {
return false, fmt.Errorf("field %q does not exist in security group", values["name"].(string))
}

if !pattern.MatchString(fmt.Sprint(value)) {
return false, nil
}
}

return true, nil
}
68 changes: 68 additions & 0 deletions cloudstack/data_source_cloudstack_security_group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// 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/hashicorp/terraform-plugin-sdk/v2/helper/id"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccSecurityGroupDataSource_basic(t *testing.T) {
resourceName := "cloudstack_security_group.security-group-resource"
dataSourceName := "data.cloudstack_security_group.security-group-data-source"
securityGroupName := "terraform-security-group-data-source-" + id.UniqueId()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testSecurityGroupDataSourceConfig(securityGroupName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"),
resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"),
resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"),
),
},
},
})
}

func testSecurityGroupDataSourceConfig(name string) string {
return fmt.Sprintf(`
resource "cloudstack_security_group" "security-group-resource" {
name = "%[1]s"
description = "Security group data source acceptance test"
}

data "cloudstack_security_group" "security-group-data-source" {
filter {
name = "name"
value = "^%[1]s$"
}
depends_on = [cloudstack_security_group.security-group-resource]
}
`,
name,
)
}
1 change: 1 addition & 0 deletions cloudstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func Provider() *schema.Provider {
"cloudstack_quota_tariff": dataSourceCloudStackQuotaTariff(),
"cloudstack_user_data": dataSourceCloudstackUserData(),
"cloudstack_kubernetes_cluster_config": dataSourceCloudstackKubernetesClusterConfig(),
"cloudstack_security_group": dataSourceCloudstackSecurityGroup(),
},

ResourcesMap: map[string]*schema.Resource{
Expand Down
1 change: 1 addition & 0 deletions website/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ The following arguments are supported:
- [instance](./d/instance.html.markdown)
- [ipaddress](./d/ipaddress.html.markdown)
- [network_offering](./d/network_offering.html.markdown)
- [security_group](./d/security_group.html.markdown)
- [service_offering](./d/service_offering.html.markdown)
- [ssh_keypair](./d/ssh_keypair.html.markdown)
- [template](./d/template.html.markdown)
Expand Down
41 changes: 41 additions & 0 deletions website/docs/d/security_group.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_security_group"
description: |-
Gets information about a security group.
---

# cloudstack_security_group

Use this data source to get information about a security group for use in other resources.

## Example Usage

```hcl
data "cloudstack_security_group" "web" {
project = "my-project"

filter {
name = "name"
value = "^web-servers$"
}
}
```

## Argument Reference

The following arguments are supported:

* `filter` - (Required) One or more name/regular-expression pairs used to select the security group. Filter names use snake case, for example `name`, `description`, or `project`.
* `project` - (Optional) The name or ID of the project containing the security group.

The filters must identify exactly one security group.

## Attributes Reference

The following attributes are exported:

* `id` - The ID of the security group.
* `name` - The name of the security group.
* `description` - The description of the security group.
* `project` - The name of the project containing the security group, or its ID when the data source was configured with a project ID.