From 7245ed9784d5527f6ee78d4f5515772f70ae4933 Mon Sep 17 00:00:00 2001 From: Stijn Simons Date: Thu, 17 Sep 2026 14:34:24 +0200 Subject: [PATCH] Add cloudstack_network datasource --- cloudstack/data_source_cloudstack_network.go | 230 ++++++++++++++++++ .../data_source_cloudstack_network_test.go | 77 ++++++ cloudstack/provider.go | 1 + website/cloudstack.erb | 3 + website/docs/d/network.html.markdown | 54 ++++ 5 files changed, 365 insertions(+) create mode 100644 cloudstack/data_source_cloudstack_network.go create mode 100644 cloudstack/data_source_cloudstack_network_test.go create mode 100644 website/docs/d/network.html.markdown diff --git a/cloudstack/data_source_cloudstack_network.go b/cloudstack/data_source_cloudstack_network.go new file mode 100644 index 00000000..df21b14d --- /dev/null +++ b/cloudstack/data_source_cloudstack_network.go @@ -0,0 +1,230 @@ +// +// 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" + "time" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackNetwork() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackNetworkRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + // Computed values + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "display_text": { + Type: schema.TypeString, + Computed: true, + }, + + "cidr": { + Type: schema.TypeString, + Computed: true, + }, + + "gateway": { + Type: schema.TypeString, + Computed: true, + }, + + "network_domain": { + Type: schema.TypeString, + Computed: true, + }, + + "network_offering_id": { + Type: schema.TypeString, + Computed: true, + }, + + "network_offering_name": { + Type: schema.TypeString, + Computed: true, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + + "vpc_id": { + Type: schema.TypeString, + Computed: true, + }, + + "acl_id": { + Type: schema.TypeString, + Computed: true, + }, + + "zone_id": { + Type: schema.TypeString, + Computed: true, + }, + + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + + "state": { + Type: schema.TypeString, + Computed: true, + }, + + "type": { + Type: schema.TypeString, + Computed: true, + }, + + "traffic_type": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func datasourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.Network.NewListNetworksParams() + + if err := setProjectid(p, cs, d); err != nil { + return err + } + + csNetworks, err := cs.Network.ListNetworks(p) + if err != nil { + return fmt.Errorf("Failed to list networks: %s", err) + } + + filters := d.Get("filter") + var networks []*cloudstack.Network + + for _, n := range csNetworks.Networks { + match, err := applyNetworkFilters(n, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + networks = append(networks, n) + } + } + + if len(networks) == 0 { + return fmt.Errorf("No network is matching with the specified regex") + } + + network, err := latestNetwork(networks) + if err != nil { + return err + } + log.Printf("[DEBUG] Selected network: %s\n", network.Name) + + return networkDescriptionAttributes(d, network) +} + +func networkDescriptionAttributes(d *schema.ResourceData, network *cloudstack.Network) error { + d.SetId(network.Id) + d.Set("name", network.Name) + d.Set("display_text", network.Displaytext) + d.Set("cidr", network.Cidr) + d.Set("gateway", network.Gateway) + d.Set("network_domain", network.Networkdomain) + d.Set("network_offering_id", network.Networkofferingid) + d.Set("network_offering_name", network.Networkofferingname) + d.Set("project", network.Project) + d.Set("project_id", network.Projectid) + d.Set("vpc_id", network.Vpcid) + d.Set("acl_id", network.Aclid) + d.Set("zone_id", network.Zoneid) + d.Set("zone_name", network.Zonename) + d.Set("state", network.State) + d.Set("type", network.Type) + d.Set("traffic_type", network.Traffictype) + d.Set("tags", tagsToMap(network.Tags)) + + return nil +} + +func latestNetwork(networks []*cloudstack.Network) (*cloudstack.Network, error) { + var latest time.Time + var network *cloudstack.Network + + for _, n := range networks { + created, err := time.Parse("2006-01-02T15:04:05-0700", n.Created) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date of a network: %s", err) + } + + if created.After(latest) { + latest = created + network = n + } + } + + return network, nil +} + +func applyNetworkFilters(network *cloudstack.Network, filters *schema.Set) (bool, error) { + var networkJSON map[string]interface{} + k, _ := json.Marshal(network) + err := json.Unmarshal(k, &networkJSON) + if err != nil { + return false, err + } + + for _, f := range filters.List() { + m := f.(map[string]interface{}) + r, err := regexp.Compile(m["value"].(string)) + if err != nil { + return false, fmt.Errorf("Invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(m["name"].(string), "_", "") + networkField := fmt.Sprintf("%v", networkJSON[updatedName]) + if !r.MatchString(networkField) { + return false, nil + } + } + + return true, nil +} diff --git a/cloudstack/data_source_cloudstack_network_test.go b/cloudstack/data_source_cloudstack_network_test.go new file mode 100644 index 00000000..5545b4cd --- /dev/null +++ b/cloudstack/data_source_cloudstack_network_test.go @@ -0,0 +1,77 @@ +// +// 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 ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccNetworkDataSource_basic(t *testing.T) { + resourceName := "cloudstack_network.network-resource" + datasourceName := "data.cloudstack_network.network-data-source" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testNetworkDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(datasourceName, "display_text", resourceName, "display_text"), + resource.TestCheckResourceAttrPair(datasourceName, "cidr", resourceName, "cidr"), + resource.TestCheckResourceAttrPair(datasourceName, "network_offering_name", resourceName, "network_offering"), + ), + }, + }, + }) +} + +const testNetworkDataSourceConfig_basic = ` +resource "cloudstack_network" "network-resource" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = "Sandbox-simulator" +} + +data "cloudstack_network" "network-data-source" { + filter { + name = "name" + value = "terraform-network" + } + + filter { + name = "cidr" + value = "10.1.1.0/24" + } + + depends_on = [ + cloudstack_network.network-resource + ] +} + +output "network-output" { + value = data.cloudstack_network.network-data-source +} +` diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 81bd20bd..843884f2 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -111,6 +111,7 @@ func Provider() *schema.Provider { "cloudstack_security_group": dataSourceCloudstackSecurityGroup(), "cloudstack_vgpu_profile": dataSourceCloudstackVgpuProfile(), "cloudstack_gpu_card": dataSourceCloudstackGpuCard(), + "cloudstack_network": dataSourceCloudstackNetwork(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/website/cloudstack.erb b/website/cloudstack.erb index 09d14d90..48f946a7 100644 --- a/website/cloudstack.erb +++ b/website/cloudstack.erb @@ -19,6 +19,9 @@ > cloudstack_role + > + cloudstack_network + diff --git a/website/docs/d/network.html.markdown b/website/docs/d/network.html.markdown new file mode 100644 index 00000000..c62828fe --- /dev/null +++ b/website/docs/d/network.html.markdown @@ -0,0 +1,54 @@ +--- +layout: "cloudstack" +page_title: "Cloudstack: cloudstack_network" +sidebar_current: "docs-cloudstack-datasource-network" +description: |- + Gets information about a CloudStack network. +--- + +# cloudstack_network + +Use this datasource to get information about a network for use in other resources. + +### Example Usage + +```hcl +data "cloudstack_network" "network-data-source" { + filter { + name = "name" + value = "test-network" + } + + filter { + name = "cidr" + value = "10.1.1.0/24" + } +} +``` + +### Argument Reference + +* `filter` - (Required) One or more name/value pairs to filter off of. You can apply filters on any exported attributes. +* `project` - (Optional) The name or ID of the project the network belongs to. + +## Attributes Reference + +The following attributes are exported: + +* `name` - The name of the network. +* `display_text` - An alternate display text of the network. +* `cidr` - The CIDR block of the network. +* `gateway` - The gateway of the network. +* `network_domain` - The network domain of the network. +* `network_offering_id` - The ID of the network offering used by the network. +* `network_offering_name` - The name of the network offering used by the network. +* `project` - The project name of the network. +* `project_id` - The project ID of the network. +* `vpc_id` - The VPC ID the network belongs to. +* `acl_id` - The ACL ID attached to the network. +* `zone_id` - The ID of the zone the network belongs to. +* `zone_name` - The name of the zone the network belongs to. +* `state` - The state of the network. +* `type` - The type of the network. +* `traffic_type` - The traffic type of the network. +* `tags` - The tags assigned to the network.