diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 00da8a92ee..83ef7713c9 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -971,7 +971,19 @@ func TestCheckModuleResourceAttr(mp []string, name string, key string, value str } func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { + // Empty containers may be elided from the state. + // If the intent here is to check for an empty container, allow the key to + // also be non-existent. + emptyCheck := false + if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { + emptyCheck = true + } + if v, ok := is.Attributes[key]; !ok || v != value { + if emptyCheck && !ok { + return nil + } + if !ok { return fmt.Errorf("%s: Attribute '%s' not found", name, key) } @@ -1014,7 +1026,20 @@ func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestChe } func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { - if _, ok := is.Attributes[key]; ok { + // Empty containers may sometimes be included in the state. + // If the intent here is to check for an empty container, allow the value to + // also be "0". + emptyCheck := false + if strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%") { + emptyCheck = true + } + + val, exists := is.Attributes[key] + if emptyCheck && val == "0" { + return nil + } + + if exists { return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) } diff --git a/helper/resource/testing_test.go b/helper/resource/testing_test.go index 59d2f17b43..885bfabf9b 100644 --- a/helper/resource/testing_test.go +++ b/helper/resource/testing_test.go @@ -1113,3 +1113,65 @@ resource "test_instance" "foo" {} const testConfigStrProvider = ` provider "test" {} ` + +func TestCheckResourceAttr_empty(t *testing.T) { + s := terraform.NewState() + s.AddModuleState(&terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_resource": &terraform.ResourceState{ + Primary: &terraform.InstanceState{ + Attributes: map[string]string{ + "empty_list.#": "0", + "empty_map.%": "0", + }, + }, + }, + }, + }) + + for _, key := range []string{ + "empty_list.#", + "empty_map.%", + "missing_list.#", + "missing_map.%", + } { + t.Run(key, func(t *testing.T) { + check := TestCheckResourceAttr("test_resource", key, "0") + if err := check(s); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestCheckNoResourceAttr_empty(t *testing.T) { + s := terraform.NewState() + s.AddModuleState(&terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_resource": &terraform.ResourceState{ + Primary: &terraform.InstanceState{ + Attributes: map[string]string{ + "empty_list.#": "0", + "empty_map.%": "0", + }, + }, + }, + }, + }) + + for _, key := range []string{ + "empty_list.#", + "empty_map.%", + "missing_list.#", + "missing_map.%", + } { + t.Run(key, func(t *testing.T) { + check := TestCheckNoResourceAttr("test_resource", key) + if err := check(s); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/website/docs/providers/index.html.markdown b/website/docs/providers/index.html.markdown index b2d6639eae..9532141d5d 100644 --- a/website/docs/providers/index.html.markdown +++ b/website/docs/providers/index.html.markdown @@ -21,165 +21,166 @@ Use the navigation to the left to find available providers by type or scroll down to see all providers. - + + - - + + + + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - -
ACME Alicloud Archive
Arukas AWSAzure Active Directory
Azure
Azure Stack BitbucketBrightbox
Brightbox CenturyLinkCloud Chef
Circonus
Cloudflare CloudScale.ch
CloudStack
Cobbler Consul
Datadog
DigitalOcean DNS
DNSMadeEasy
DNSimple Docker
Dyn
External F5 BIG-IP
Fastly
FlexibleEngine GitHub
Gitlab
Google Cloud Grafana
Hedvig
Helm Heroku
Hetzner Cloud
HTTP HuaweiCloud
Icinga2
Ignition InfluxDB
Kubernetes
Librato Linode
Local
Logentries LogicMonitor
Mailgun
MySQL Netlify
New Relic
Nomad NS1
Null
Nutanix 1&1
OpenStack
OpenTelekomCloud OpsGenie
Oracle Cloud Infrastructure
Oracle Cloud Platform Oracle Public Cloud
OVH
Packet PagerDuty
Palo Alto Networks
PostgreSQL PowerDNS
ProfitBricks
RabbitMQ Rancher
Random
RightScale Rundeck
RunScope
Scaleway Selectel
Skytap
SoftLayer StatusCake
Spotinst
TelefonicaOpenCloud Template
TencentCloud
Terraform Terraform Enterprise
TLS
Triton UCloud
UltraDNS
Vault VMware vCloud Director
VMware NSX-T
VMware vSphere