diff --git a/cloudstack/data_source_cloudstack_instance.go b/cloudstack/data_source_cloudstack_instance.go index a6017225..8073e2d0 100644 --- a/cloudstack/data_source_cloudstack_instance.go +++ b/cloudstack/data_source_cloudstack_instance.go @@ -207,7 +207,11 @@ func applyInstanceFilters(instance *cloudstack.VirtualMachine, filters *schema.S return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - instanceField := instanceJSON[updatedName].(string) + instanceFieldValue, instanceFieldFound := instanceJSON[updatedName] + if !instanceFieldFound || instanceFieldValue == nil { + return false, nil + } + instanceField := fmt.Sprintf("%v", instanceFieldValue) if !r.MatchString(instanceField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_ipaddress.go b/cloudstack/data_source_cloudstack_ipaddress.go index 997a3a26..367d62c2 100644 --- a/cloudstack/data_source_cloudstack_ipaddress.go +++ b/cloudstack/data_source_cloudstack_ipaddress.go @@ -165,7 +165,11 @@ func applyIPAddressFilters(publicIpAddress *cloudstack.PublicIpAddress, filters return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - publicIPAdressField := fmt.Sprintf("%v", publicIPAdressJSON[updatedName]) + publicIPAdressFieldValue, publicIPAdressFieldFound := publicIPAdressJSON[updatedName] + if !publicIPAdressFieldFound || publicIPAdressFieldValue == nil { + return false, nil + } + publicIPAdressField := fmt.Sprintf("%v", publicIPAdressFieldValue) if !r.MatchString(publicIPAdressField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_network_offering.go b/cloudstack/data_source_cloudstack_network_offering.go index fe82ebe3..b68cce37 100644 --- a/cloudstack/data_source_cloudstack_network_offering.go +++ b/cloudstack/data_source_cloudstack_network_offering.go @@ -245,7 +245,11 @@ func applyNetworkOfferingFilters(networkOffering *cloudstack.NetworkOffering, fi return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - networkOfferingField := networkOfferingJSON[updatedName].(string) + networkOfferingFieldValue, networkOfferingFieldFound := networkOfferingJSON[updatedName] + if !networkOfferingFieldFound || networkOfferingFieldValue == nil { + return false, nil + } + networkOfferingField := fmt.Sprintf("%v", networkOfferingFieldValue) if !r.MatchString(networkOfferingField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_physical_network.go b/cloudstack/data_source_cloudstack_physical_network.go index 6a436051..7e37644f 100644 --- a/cloudstack/data_source_cloudstack_physical_network.go +++ b/cloudstack/data_source_cloudstack_physical_network.go @@ -131,7 +131,11 @@ func applyPhysicalNetworkFilters(physicalNetwork *cloudstack.PhysicalNetwork, fi return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - physicalNetworkField := physicalNetworkJSON[updatedName].(string) + physicalNetworkFieldValue, physicalNetworkFieldFound := physicalNetworkJSON[updatedName] + if !physicalNetworkFieldFound || physicalNetworkFieldValue == nil { + return false, nil + } + physicalNetworkField := fmt.Sprintf("%v", physicalNetworkFieldValue) if !r.MatchString(physicalNetworkField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_service_offering.go b/cloudstack/data_source_cloudstack_service_offering.go index 8c1272a3..3ca2ad26 100644 --- a/cloudstack/data_source_cloudstack_service_offering.go +++ b/cloudstack/data_source_cloudstack_service_offering.go @@ -128,7 +128,11 @@ func applyServiceOfferingFilters(serviceOffering *cloudstack.ServiceOffering, fi return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - serviceOfferingField := serviceOfferingJSON[updatedName].(string) + serviceOfferingFieldValue, serviceOfferingFieldFound := serviceOfferingJSON[updatedName] + if !serviceOfferingFieldFound || serviceOfferingFieldValue == nil { + return false, nil + } + serviceOfferingField := fmt.Sprintf("%v", serviceOfferingFieldValue) if !r.MatchString(serviceOfferingField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_ssh_keypair.go b/cloudstack/data_source_cloudstack_ssh_keypair.go index 1cb2734f..5cfc8d86 100644 --- a/cloudstack/data_source_cloudstack_ssh_keypair.go +++ b/cloudstack/data_source_cloudstack_ssh_keypair.go @@ -103,7 +103,11 @@ func applySshKeyPairsFilters(sshKeyPair *cloudstack.SSHKeyPair, filters *schema. return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - sshKeyPairField := sshKeyPairJSON[updatedName].(string) + sshKeyPairFieldValue, sshKeyPairFieldFound := sshKeyPairJSON[updatedName] + if !sshKeyPairFieldFound || sshKeyPairFieldValue == nil { + return false, nil + } + sshKeyPairField := fmt.Sprintf("%v", sshKeyPairFieldValue) if !r.MatchString(sshKeyPairField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_template.go b/cloudstack/data_source_cloudstack_template.go index 5040f9bb..48183f56 100644 --- a/cloudstack/data_source_cloudstack_template.go +++ b/cloudstack/data_source_cloudstack_template.go @@ -182,7 +182,11 @@ func applyFilters(template *cloudstack.Template, filters *schema.Set) (bool, err return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - templateField := templateJSON[updatedName].(string) + templateFieldValue, templateFieldFound := templateJSON[updatedName] + if !templateFieldFound || templateFieldValue == nil { + return false, nil + } + templateField := fmt.Sprintf("%v", templateFieldValue) if !r.MatchString(templateField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_user.go b/cloudstack/data_source_cloudstack_user.go index 23516f1c..8a2285ac 100644 --- a/cloudstack/data_source_cloudstack_user.go +++ b/cloudstack/data_source_cloudstack_user.go @@ -145,7 +145,11 @@ func applyUserFilters(user *cloudstack.User, filters *schema.Set) (bool, error) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") log.Print(updatedName) - userField := userJSON[updatedName].(string) + userFieldValue, userFieldFound := userJSON[updatedName] + if !userFieldFound || userFieldValue == nil { + return false, nil + } + userField := fmt.Sprintf("%v", userFieldValue) if !r.MatchString(userField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_volume.go b/cloudstack/data_source_cloudstack_volume.go index 2e548eb0..09bb5892 100644 --- a/cloudstack/data_source_cloudstack_volume.go +++ b/cloudstack/data_source_cloudstack_volume.go @@ -133,7 +133,11 @@ func applyVolumeFilters(volume *cloudstack.Volume, filters *schema.Set) (bool, e return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - volume := volumeJSON[updatedName].(string) + volumeValue, volumeFound := volumeJSON[updatedName] + if !volumeFound || volumeValue == nil { + return false, nil + } + volume := fmt.Sprintf("%v", volumeValue) if !r.MatchString(volume) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_vpc.go b/cloudstack/data_source_cloudstack_vpc.go index 499ef373..92a4513e 100644 --- a/cloudstack/data_source_cloudstack_vpc.go +++ b/cloudstack/data_source_cloudstack_vpc.go @@ -171,7 +171,11 @@ func applyVPCFilters(vpc *cloudstack.VPC, filters *schema.Set) (bool, error) { } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") log.Print(updatedName) - vpcField := vpcJSON[updatedName].(string) + vpcFieldValue, vpcFieldFound := vpcJSON[updatedName] + if !vpcFieldFound || vpcFieldValue == nil { + return false, nil + } + vpcField := fmt.Sprintf("%v", vpcFieldValue) if !r.MatchString(vpcField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_vpc_offering.go b/cloudstack/data_source_cloudstack_vpc_offering.go index d97d0c53..75834a9d 100644 --- a/cloudstack/data_source_cloudstack_vpc_offering.go +++ b/cloudstack/data_source_cloudstack_vpc_offering.go @@ -220,7 +220,11 @@ func applyVPCOfferingFilters(vpcOffering *cloudstack.VPCOffering, filters *schem return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - vpcOfferingField := fmt.Sprintf("%v", vpcOfferingJSON[updatedName]) + vpcOfferingFieldValue, vpcOfferingFieldFound := vpcOfferingJSON[updatedName] + if !vpcOfferingFieldFound || vpcOfferingFieldValue == nil { + return false, nil + } + vpcOfferingField := fmt.Sprintf("%v", vpcOfferingFieldValue) if !r.MatchString(vpcOfferingField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_vpn_connection.go b/cloudstack/data_source_cloudstack_vpn_connection.go index 90bbeedc..5062746c 100644 --- a/cloudstack/data_source_cloudstack_vpn_connection.go +++ b/cloudstack/data_source_cloudstack_vpn_connection.go @@ -131,7 +131,11 @@ func applyVPNConnectionFilters(vpnConnection *cloudstack.VpnConnection, filters } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") log.Print(updatedName) - vpnConnectionField := vpnConnectionJSON[updatedName].(string) + vpnConnectionFieldValue, vpnConnectionFieldFound := vpnConnectionJSON[updatedName] + if !vpnConnectionFieldFound || vpnConnectionFieldValue == nil { + return false, nil + } + vpnConnectionField := fmt.Sprintf("%v", vpnConnectionFieldValue) if !r.MatchString(vpnConnectionField) { return false, nil } diff --git a/cloudstack/data_source_cloudstack_zone.go b/cloudstack/data_source_cloudstack_zone.go index 9fb211b2..5bdb320a 100644 --- a/cloudstack/data_source_cloudstack_zone.go +++ b/cloudstack/data_source_cloudstack_zone.go @@ -111,7 +111,11 @@ func applyZoneFilters(zone *cloudstack.Zone, filters *schema.Set) (bool, error) return false, fmt.Errorf("Invalid regex: %s", err) } updatedName := strings.ReplaceAll(m["name"].(string), "_", "") - zoneField := zoneJSON[updatedName].(string) + zoneFieldValue, zoneFieldFound := zoneJSON[updatedName] + if !zoneFieldFound || zoneFieldValue == nil { + return false, nil + } + zoneField := fmt.Sprintf("%v", zoneFieldValue) if !r.MatchString(zoneField) { return false, nil } diff --git a/cloudstack/data_source_filter_panic_unit_test.go b/cloudstack/data_source_filter_panic_unit_test.go new file mode 100644 index 00000000..fcc465ef --- /dev/null +++ b/cloudstack/data_source_filter_panic_unit_test.go @@ -0,0 +1,70 @@ +// 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/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dsPanicFilterSet(pairs ...[2]string) *schema.Set { + hash := func(i interface{}) int { + m := i.(map[string]interface{}) + return schema.HashString(m["name"].(string) + "|" + m["value"].(string)) + } + items := make([]interface{}, 0, len(pairs)) + for _, p := range pairs { + items = append(items, map[string]interface{}{"name": p[0], "value": p[1]}) + } + return schema.NewSet(hash, items) +} + +// A data-source filter referencing an unknown field name, or a non-string field, must not panic. +func TestApplyVolumeFiltersDoesNotPanicOnUnknownOrNonStringField(t *testing.T) { + vol := &cloudstack.Volume{Name: "vol-a", Size: 5} + + // Unknown filter field: the JSON lookup returns nil; must not panic and must not match. + if match, err := applyVolumeFilters(vol, dsPanicFilterSet([2]string{"no_such_field", "x"})); err != nil { + t.Fatalf("unexpected error: %s", err) + } else if match { + t.Errorf("an unknown filter field should not match") + } + + // A permissive regex must not match a field that does not exist. + if match, err := applyVolumeFilters(vol, dsPanicFilterSet([2]string{"no_such_field", ".*"})); err != nil { + t.Fatalf("unexpected error: %s", err) + } else if match { + t.Errorf("a nonexistent field should not match even a permissive regex") + } + + // A numeric field is stringified and matched against the regex. + if match, err := applyVolumeFilters(vol, dsPanicFilterSet([2]string{"size", "^5$"})); err != nil { + t.Fatalf("unexpected error: %s", err) + } else if !match { + t.Errorf("a numeric field should match its string representation") + } + + // An existing field must still match. + if match, err := applyVolumeFilters(vol, dsPanicFilterSet([2]string{"name", "vol-a"})); err != nil { + t.Fatalf("unexpected error: %s", err) + } else if !match { + t.Errorf("an existing field that matches the regex should match") + } +}