Repository: GoogleCloudPlatform/terraform-validator
Pull request #990 (2022-09-23 14:45:37)

Replace errors.Wrap with fmt.Error
b/234884313 
Code cleanup, replace errors.Wrap with fmt.Error


Patch:
diff --git a/ancestrymanager/ancestrymanager.go b/ancestrymanager/ancestrymanager.go
index b0e2f70a..7961cbd6 100644
--- a/ancestrymanager/ancestrymanager.go
+++ b/ancestrymanager/ancestrymanager.go
@@ -143,7 +143,7 @@ func (m *manager) fetchAncestors(config *resources.Config, tfData resources.Terr
 	switch cai.Type {
 	case "cloudresourcemanager.googleapis.com/Folder":
 		if !folderOK {
-			return nil, fmt.Errorf("folder id not found in terraform data")
+			return []string{"organizations/unknown"}, nil
 		}
 		key = folderKey
 	case "cloudresourcemanager.googleapis.com/Organization":
diff --git a/ancestrymanager/ancestryutil.go b/ancestrymanager/ancestryutil.go
index 088b69d3..c5a28a67 100644
--- a/ancestrymanager/ancestryutil.go
+++ b/ancestrymanager/ancestryutil.go
@@ -18,12 +18,15 @@ func assetParent(cai *resources.Asset, ancestors []string) (string, error) {
 	}
 	switch cai.Type {
 	case "cloudresourcemanager.googleapis.com/Folder":
-		if len(ancestors) < 2 {
-			return "", fmt.Errorf("unexpected value for ancestors: %s", ancestors)
+		if len(ancestors) > 1 {
+			parent := ancestors[1]
+			if strings.HasPrefix(parent, "folders/") || strings.HasPrefix(parent, "organizations/") {
+				return fmt.Sprintf("//cloudresourcemanager.googleapis.com/%s", ancestors[1]), nil
+			}
 		}
-		parent := ancestors[1]
-		if strings.HasPrefix(parent, "folders/") || strings.HasPrefix(parent, "organizations/") {
-			return fmt.Sprintf("//cloudresourcemanager.googleapis.com/%s", ancestors[1]), nil
+		if len(ancestors) == 1 && strings.HasPrefix(ancestors[0], "organizations/") {
+			// organizations/unknown
+			return fmt.Sprintf("//cloudresourcemanager.googleapis.com/%s", ancestors[0]), nil
 		}
 	case "cloudresourcemanager.googleapis.com/Organization":
 		return "", nil
diff --git a/cmd/convert.go b/cmd/convert.go
index 7557c98b..19086189 100644
--- a/cmd/convert.go
+++ b/cmd/convert.go
@@ -133,7 +133,7 @@ func (o *convertOptions) run(plan string) error {
 		defer f.Close()
 
 		if err := json.NewEncoder(f).Encode(assets); err != nil {
-			return errors.Wrap(err, "encoding json")
+			return fmt.Errorf("encoding json: %w", err)
 		}
 		return nil
 	}
@@ -148,7 +148,7 @@ func (o *convertOptions) run(plan string) error {
 
 	// Legacy behavior
 	if err := json.NewEncoder(os.Stdout).Encode(assets); err != nil {
-		return errors.Wrap(err, "encoding json")
+		return fmt.Errorf("encoding json: %w", err)
 	}
 	return nil
 }
diff --git a/cmd/validate.go b/cmd/validate.go
index 64732517..765c20e9 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -137,7 +137,7 @@ func (o *validateOptions) run(plan string) error {
 
 	violations, err := o.validateAssets(ctx, assets, o.policyPath)
 	if err != nil {
-		return errors.Wrap(err, "validating")
+		return fmt.Errorf("validating: %w", err)
 	}
 
 	if o.rootOptions.useStructuredLogging {
@@ -162,7 +162,7 @@ func (o *validateOptions) run(plan string) error {
 			auditResult := &validator.AuditResponse{}
 			auditResult.Violations = violations
 			if err := marshaller.Marshal(os.Stdout, auditResult); err != nil {
-				return errors.Wrap(err, "marshalling violations to json")
+				return fmt.Errorf("marshalling violations to json: %w", err)
 			}
 		} else {
 			fmt.Print("Found Violations:\n\n")
diff --git a/converters/google/resources/bigqueryreservation_reservation.go b/converters/google/resources/bigqueryreservation_reservation.go
index a250efef..34060636 100644
--- a/converters/google/resources/bigqueryreservation_reservation.go
+++ b/converters/google/resources/bigqueryreservation_reservation.go
@@ -60,6 +60,18 @@ func GetBigqueryReservationReservationApiObject(d TerraformResourceData, config
 	} else if v, ok := d.GetOkExists("ignore_idle_slots"); !isEmptyValue(reflect.ValueOf(ignoreIdleSlotsProp)) && (ok || !reflect.DeepEqual(v, ignoreIdleSlotsProp)) {
 		obj["ignoreIdleSlots"] = ignoreIdleSlotsProp
 	}
+	concurrencyProp, err := expandBigqueryReservationReservationConcurrency(d.Get("concurrency"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("concurrency"); !isEmptyValue(reflect.ValueOf(concurrencyProp)) && (ok || !reflect.DeepEqual(v, concurrencyProp)) {
+		obj["concurrency"] = concurrencyProp
+	}
+	multiRegionAuxiliaryProp, err := expandBigqueryReservationReservationMultiRegionAuxiliary(d.Get("multi_region_auxiliary"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("multi_region_auxiliary"); !isEmptyValue(reflect.ValueOf(multiRegionAuxiliaryProp)) && (ok || !reflect.DeepEqual(v, multiRegionAuxiliaryProp)) {
+		obj["multiRegionAuxiliary"] = multiRegionAuxiliaryProp
+	}
 
 	return obj, nil
 }
@@ -71,3 +83,11 @@ func expandBigqueryReservationReservationSlotCapacity(v interface{}, d Terraform
 func expandBigqueryReservationReservationIgnoreIdleSlots(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	return v, nil
 }
+
+func expandBigqueryReservationReservationConcurrency(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandBigqueryReservationReservationMultiRegionAuxiliary(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
diff --git a/converters/google/resources/certificatemanager_certificate.go b/converters/google/resources/certificatemanager_certificate.go
index 7abc673c..e1cedfde 100644
--- a/converters/google/resources/certificatemanager_certificate.go
+++ b/converters/google/resources/certificatemanager_certificate.go
@@ -121,6 +121,20 @@ func expandCertificateManagerCertificateSelfManaged(v interface{}, d TerraformRe
 	original := raw.(map[string]interface{})
 	transformed := make(map[string]interface{})
 
+	transformedCertificatePem, err := expandCertificateManagerCertificateSelfManagedCertificatePem(original["certificate_pem"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedCertificatePem); val.IsValid() && !isEmptyValue(val) {
+		transformed["certificatePem"] = transformedCertificatePem
+	}
+
+	transformedPrivateKeyPem, err := expandCertificateManagerCertificateSelfManagedPrivateKeyPem(original["private_key_pem"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedPrivateKeyPem); val.IsValid() && !isEmptyValue(val) {
+		transformed["privateKeyPem"] = transformedPrivateKeyPem
+	}
+
 	transformedPemCertificate, err := expandCertificateManagerCertificateSelfManagedPemCertificate(original["pem_certificate"], d, config)
 	if err != nil {
 		return nil, err
@@ -138,6 +152,14 @@ func expandCertificateManagerCertificateSelfManaged(v interface{}, d TerraformRe
 	return transformed, nil
 }
 
+func expandCertificateManagerCertificateSelfManagedCertificatePem(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandCertificateManagerCertificateSelfManagedPrivateKeyPem(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
 func expandCertificateManagerCertificateSelfManagedPemCertificate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	return v, nil
 }
diff --git a/converters/google/resources/compute_backend_bucket.go b/converters/google/resources/compute_backend_bucket.go
index 59aafb50..56cf4da4 100644
--- a/converters/google/resources/compute_backend_bucket.go
+++ b/converters/google/resources/compute_backend_bucket.go
@@ -60,6 +60,12 @@ func GetComputeBackendBucketApiObject(d TerraformResourceData, config *Config) (
 	} else if v, ok := d.GetOkExists("cdn_policy"); !isEmptyValue(reflect.ValueOf(cdnPolicyProp)) && (ok || !reflect.DeepEqual(v, cdnPolicyProp)) {
 		obj["cdnPolicy"] = cdnPolicyProp
 	}
+	compressionModeProp, err := expandComputeBackendBucketCompressionMode(d.Get("compression_mode"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("compression_mode"); !isEmptyValue(reflect.ValueOf(compressionModeProp)) && (ok || !reflect.DeepEqual(v, compressionModeProp)) {
+		obj["compressionMode"] = compressionModeProp
+	}
 	edgeSecurityPolicyProp, err := expandComputeBackendBucketEdgeSecurityPolicy(d.Get("edge_security_policy"), d, config)
 	if err != nil {
 		return nil, err
@@ -316,6 +322,10 @@ func expandComputeBackendBucketCdnPolicyBypassCacheOnRequestHeadersHeaderName(v
 	return v, nil
 }
 
+func expandComputeBackendBucketCompressionMode(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
 func expandComputeBackendBucketEdgeSecurityPolicy(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	return v, nil
 }
diff --git a/converters/google/resources/compute_backend_service.go b/converters/google/resources/compute_backend_service.go
index 4d72bd20..ef1b009a 100644
--- a/converters/google/resources/compute_backend_service.go
+++ b/converters/google/resources/compute_backend_service.go
@@ -221,6 +221,12 @@ func GetComputeBackendServiceApiObject(d TerraformResourceData, config *Config)
 	} else if v, ok := d.GetOkExists("circuit_breakers"); !isEmptyValue(reflect.ValueOf(circuitBreakersProp)) && (ok || !reflect.DeepEqual(v, circuitBreakersProp)) {
 		obj["circuitBreakers"] = circuitBreakersProp
 	}
+	compressionModeProp, err := expandComputeBackendServiceCompressionMode(d.Get("compression_mode"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("compression_mode"); !isEmptyValue(reflect.ValueOf(compressionModeProp)) && (ok || !reflect.DeepEqual(v, compressionModeProp)) {
+		obj["compressionMode"] = compressionModeProp
+	}
 	consistentHashProp, err := expandComputeBackendServiceConsistentHash(d.Get("consistent_hash"), d, config)
 	if err != nil {
 		return nil, err
@@ -598,6 +604,10 @@ func expandComputeBackendServiceCircuitBreakersMaxRetries(v interface{}, d Terra
 	return v, nil
 }
 
+func expandComputeBackendServiceCompressionMode(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
 func expandComputeBackendServiceConsistentHash(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	l := v.([]interface{})
 	if len(l) == 0 || l[0] == nil {
diff --git a/converters/google/resources/dns_managed_zone.go b/converters/google/resources/dns_managed_zone.go
index 786f2756..081143f6 100644
--- a/converters/google/resources/dns_managed_zone.go
+++ b/converters/google/resources/dns_managed_zone.go
@@ -107,6 +107,12 @@ func GetDNSManagedZoneApiObject(d TerraformResourceData, config *Config) (map[st
 	} else if v, ok := d.GetOkExists("peering_config"); !isEmptyValue(reflect.ValueOf(peeringConfigProp)) && (ok || !reflect.DeepEqual(v, peeringConfigProp)) {
 		obj["peeringConfig"] = peeringConfigProp
 	}
+	cloudLoggingConfigProp, err := expandDNSManagedZoneCloudLoggingConfig(d.Get("cloud_logging_config"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("cloud_logging_config"); !isEmptyValue(reflect.ValueOf(cloudLoggingConfigProp)) && (ok || !reflect.DeepEqual(v, cloudLoggingConfigProp)) {
+		obj["cloudLoggingConfig"] = cloudLoggingConfigProp
+	}
 
 	return obj, nil
 }
@@ -415,3 +421,26 @@ func expandDNSManagedZonePeeringConfigTargetNetworkNetworkUrl(v interface{}, d T
 	}
 	return ConvertSelfLinkToV1(url), nil
 }
+
+func expandDNSManagedZoneCloudLoggingConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 || l[0] == nil {
+		return nil, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedEnableLogging, err := expandDNSManagedZoneCloudLoggingConfigEnableLogging(original["enable_logging"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedEnableLogging); val.IsValid() && !isEmptyValue(val) {
+		transformed["enableLogging"] = transformedEnableLogging
+	}
+
+	return transformed, nil
+}
+
+func expandDNSManagedZoneCloudLoggingConfigEnableLogging(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
diff --git a/converters/google/resources/folder_iam.go b/converters/google/resources/folder_iam.go
index 8284d6fd..0a9fa366 100644
--- a/converters/google/resources/folder_iam.go
+++ b/converters/google/resources/folder_iam.go
@@ -89,6 +89,10 @@ func newFolderIamAsset(
 }
 
 func FetchFolderIamPolicy(d TerraformResourceData, config *Config) (Asset, error) {
+	if _, ok := d.GetOk("folder"); !ok {
+		return Asset{}, ErrEmptyIdentityField
+	}
+
 	return fetchIamPolicy(
 		NewFolderIamUpdater,
 		d,
diff --git a/converters/google/resources/getconfig.go b/converters/google/resources/getconfig.go
index 60210fca..34d8f17e 100644
--- a/converters/google/resources/getconfig.go
+++ b/converters/google/resources/getconfig.go
@@ -2,9 +2,8 @@ package google
 
 import (
 	"context"
+	"fmt"
 	"net/http"
-
-	"github.com/pkg/errors"
 )
 
 // Return the value of the private userAgent field
@@ -43,7 +42,7 @@ func NewConfig(ctx context.Context, project, zone, region string, offline bool,
 	if !offline {
 		ConfigureBasePaths(cfg)
 		if err := cfg.LoadAndValidate(ctx); err != nil {
-			return nil, errors.Wrap(err, "load and validate config")
+			return nil, fmt.Errorf("load and validate config: %w", err)
 		}
 		if client != nil {
 			cfg.client = client
diff --git a/converters/google/resources/identityplatform_config.go b/converters/google/resources/identityplatform_config.go
new file mode 100644
index 00000000..f37e90c9
--- /dev/null
+++ b/converters/google/resources/identityplatform_config.go
@@ -0,0 +1,63 @@
+// ----------------------------------------------------------------------------
+//
+//     ***     AUTO GENERATED CODE    ***    Type: MMv1     ***
+//
+// ----------------------------------------------------------------------------
+//
+//     This file is automatically generated by Magic Modules and manual
+//     changes will be clobbered when the file is regenerated.
+//
+//     Please read more about how to change this file in
+//     .github/CONTRIBUTING.md.
+//
+// ----------------------------------------------------------------------------
+
+package google
+
+import "reflect"
+
+const IdentityPlatformConfigAssetType string = "identitytoolkit.googleapis.com/Config"
+
+func resourceConverterIdentityPlatformConfig() ResourceConverter {
+	return ResourceConverter{
+		AssetType: IdentityPlatformConfigAssetType,
+		Convert:   GetIdentityPlatformConfigCaiObject,
+	}
+}
+
+func GetIdentityPlatformConfigCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	name, err := assetName(d, config, "//identitytoolkit.googleapis.com/projects/{{project}}/config")
+	if err != nil {
+		return []Asset{}, err
+	}
+	if obj, err := GetIdentityPlatformConfigApiObject(d, config); err == nil {
+		return []Asset{{
+			Name: name,
+			Type: IdentityPlatformConfigAssetType,
+			Resource: &AssetResource{
+				Version:              "v2",
+				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/identitytoolkit/v2/rest",
+				DiscoveryName:        "Config",
+				Data:                 obj,
+			},
+		}}, nil
+	} else {
+		return []Asset{}, err
+	}
+}
+
+func GetIdentityPlatformConfigApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
+	obj := make(map[string]interface{})
+	autodeleteAnonymousUsersProp, err := expandIdentityPlatformConfigAutodeleteAnonymousUsers(d.Get("autodelete_anonymous_users"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("autodelete_anonymous_users"); !isEmptyValue(reflect.ValueOf(autodeleteAnonymousUsersProp)) && (ok || !reflect.DeepEqual(v, autodeleteAnonymousUsersProp)) {
+		obj["autodeleteAnonymousUsers"] = autodeleteAnonymousUsersProp
+	}
+
+	return obj, nil
+}
+
+func expandIdentityPlatformConfigAutodeleteAnonymousUsers(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
diff --git a/converters/google/resources/retry_utils.go b/converters/google/resources/retry_utils.go
index 81517ec6..593a3ba2 100644
--- a/converters/google/resources/retry_utils.go
+++ b/converters/google/resources/retry_utils.go
@@ -52,3 +52,34 @@ func isRetryableError(topErr error, customPredicates ...RetryErrorPredicateFunc)
 	})
 	return isRetryable
 }
+
+// The polling overrides the default backoff logic with max backoff of 10s. The poll interval can be greater than 10s.
+func retryWithPolling(retryFunc func() (interface{}, error), timeout time.Duration, pollInterval time.Duration, errorRetryPredicates ...RetryErrorPredicateFunc) (interface{}, error) {
+	refreshFunc := func() (interface{}, string, error) {
+		result, err := retryFunc()
+		if err == nil {
+			return result, "done", nil
+		}
+
+		// Check if it is a retryable error.
+		if isRetryableError(err, errorRetryPredicates...) {
+			return result, "retrying", nil
+		}
+
+		// The error is not retryable.
+		return result, "done", err
+	}
+	stateChange := &resource.StateChangeConf{
+		Pending: []string{
+			"retrying",
+		},
+		Target: []string{
+			"done",
+		},
+		Refresh:      refreshFunc,
+		Timeout:      timeout,
+		PollInterval: pollInterval,
+	}
+
+	return stateChange.WaitForState()
+}
diff --git a/converters/google/resources/utils.go b/converters/google/resources/utils.go
index 77178512..1b932c4c 100644
--- a/converters/google/resources/utils.go
+++ b/converters/google/resources/utils.go
@@ -551,3 +551,11 @@ func checkGCSName(name string) error {
 	}
 	return nil
 }
+
+// checkGoogleIamPolicy makes assertions about the contents of a google_iam_policy data source's policy_data attribute
+func checkGoogleIamPolicy(value string) error {
+	if strings.Contains(value, "\"description\":\"\"") {
+		return fmt.Errorf("found an empty description field (should be omitted) in google_iam_policy data source: %s", value)
+	}
+	return nil
+}
diff --git a/go.mod b/go.mod
index bab6b97e..9c6c311e 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
 	github.com/hashicorp/go-cleanhttp v0.5.2
 	github.com/hashicorp/terraform-json v0.14.0
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.18.0
-	github.com/hashicorp/terraform-provider-google v1.20.1-0.20220922204924-47ad19be6922
+	github.com/hashicorp/terraform-provider-google v1.20.1-0.20220930122549-301043d2371d
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.8.1
diff --git a/go.sum b/go.sum
index f2ff15df..7eac4655 100644
--- a/go.sum
+++ b/go.sum
@@ -650,8 +650,8 @@ github.com/hashicorp/terraform-plugin-log v0.4.1 h1:xpbmVhvuU3mgHzLetOmx9pkOL2rm
 github.com/hashicorp/terraform-plugin-log v0.4.1/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.18.0 h1:/cdI5di5XA+N80gXzXF4YcHq36DprBskubk6Z8i26ZQ=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.18.0/go.mod h1:L3SHkD/Q8zPVgXviQmpVwy9nKwpXXZscVIpVEnQ/T50=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20220922204924-47ad19be6922 h1:0N/1w4tblFU6uSXXgv00n8Yp7U1ZWd4+3VtdMFcPhBE=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20220922204924-47ad19be6922/go.mod h1:XkWEq9m0whYwjW0vmHmzjskXkneDxOIKcAYQNaAyo7s=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20220930122549-301043d2371d h1:p/LxNAKfP+uGvE41vCXH0uCetPLYxkXwqZe618O/vUU=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20220930122549-301043d2371d/go.mod h1:XkWEq9m0whYwjW0vmHmzjskXkneDxOIKcAYQNaAyo7s=
 github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
 github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
 github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
diff --git a/tfgcv/planned_assets.go b/tfgcv/planned_assets.go
index e141f5ee..7ec2b984 100644
--- a/tfgcv/planned_assets.go
+++ b/tfgcv/planned_assets.go
@@ -64,12 +64,12 @@ func ReadPlannedAssets(ctx context.Context, path, project, zone, region string,
 func newConverter(ctx context.Context, path, project, zone, region string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger, userAgent string) (*google.Converter, error) {
 	cfg, err := resources.NewConfig(ctx, project, zone, region, offline, userAgent, nil)
 	if err != nil {
-		return nil, errors.Wrap(err, "building google configuration")
+		return nil, fmt.Errorf("building google configuration: %w", err)
 	}
 
 	ancestryManager, err := ancestrymanager.New(cfg, offline, ancestry, errorLogger)
 	if err != nil {
-		return nil, errors.Wrap(err, "building google ancestry manager")
+		return nil, fmt.Errorf("building google ancestry manager: %w", err)
 	}
 	converter := google.NewConverter(cfg, ancestryManager, offline, convertUnchanged, errorLogger)
 	return converter, nil
@@ -82,7 +82,7 @@ func readTF12Data(path string) ([]byte, error) {
 	// JSON format means Terraform 12
 	data, err := ioutil.ReadFile(path)
 	if err != nil {
-		return nil, errors.Wrap(err, "opening JSON plan file")
+		return nil, fmt.Errorf("opening JSON plan file: %w", err)
 	}
 	return data, nil
 }
diff --git a/tfgcv/proto.go b/tfgcv/proto.go
index f7fb32dc..b4173585 100644
--- a/tfgcv/proto.go
+++ b/tfgcv/proto.go
@@ -16,10 +16,10 @@ package tfgcv
 
 import (
 	"encoding/json"
+	"fmt"
 
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/golang/protobuf/proto"
-	"github.com/pkg/errors"
 )
 
 // protoViaJSON uses JSON as an intermediary serialization to convert a value into
@@ -27,11 +27,11 @@ import (
 func protoViaJSON(from interface{}, to proto.Message) error {
 	jsn, err := json.Marshal(from)
 	if err != nil {
-		return errors.Wrap(err, "marshaling to json")
+		return fmt.Errorf("marshaling to json: %w", err)
 	}
 
 	if err := jsonpb.UnmarshalString(string(jsn), to); err != nil {
-		return errors.Wrap(err, "unmarshaling to proto")
+		return fmt.Errorf("unmarshaling to proto: %w", err)
 	}
 
 	return nil
diff --git a/tfgcv/validate_assets.go b/tfgcv/validate_assets.go
index 2427cd53..aea41abb 100644
--- a/tfgcv/validate_assets.go
+++ b/tfgcv/validate_assets.go
@@ -24,7 +24,6 @@ import (
 	"github.com/GoogleCloudPlatform/config-validator/pkg/gcv"
 
 	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
-	"github.com/pkg/errors"
 )
 
 type ValidateAssetsFunc func(ctx context.Context, assets []google.Asset, policyRootPath string) ([]*validator.Violation, error)
@@ -51,14 +50,14 @@ func ValidateAssets(ctx context.Context, assets []google.Asset, policyRootPath s
 func ValidateAssetsWithLibrary(ctx context.Context, assets []google.Asset, policyPaths []string, policyLibraryDir string) ([]*validator.Violation, error) {
 	valid, err := gcv.NewValidator(policyPaths, policyLibraryDir)
 	if err != nil {
-		return nil, errors.Wrap(err, "initializing gcv validator")
+		return nil, fmt.Errorf("initializing gcv validator: %w", err)
 	}
 
 	pbAssets := make([]*validator.Asset, len(assets))
 	for i := range assets {
 		pbAssets[i] = &validator.Asset{}
 		if err := protoViaJSON(assets[i], pbAssets[i]); err != nil {
-			return nil, errors.Wrapf(err, "converting asset %s to proto", assets[i].Name)
+			return nil, fmt.Errorf("converting asset %s to proto: %w", assets[i].Name, err)
 		}
 	}
 
@@ -71,7 +70,7 @@ func ValidateAssetsWithLibrary(ctx context.Context, assets []google.Asset, polic
 		newViolations, err := valid.ReviewAsset(context.Background(), asset)
 
 		if err != nil {
-			return nil, errors.Wrapf(err, "reviewing asset %s", asset)
+			return nil, fmt.Errorf("reviewing asset %s: %w", asset, err)
 		}
 		violations = append(violations, newViolations...)
 	}
diff --git a/tfplan/json_plan.go b/tfplan/json_plan.go
index 2a09a9f8..bdb6a7b4 100644
--- a/tfplan/json_plan.go
+++ b/tfplan/json_plan.go
@@ -4,7 +4,7 @@
 // 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
+//	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,
@@ -14,8 +14,9 @@
 package tfplan
 
 import (
+	"fmt"
+
 	tfjson "github.com/hashicorp/terraform-json"
-	"github.com/pkg/errors"
 )
 
 func IsCreate(rc *tfjson.ResourceChange) bool {
@@ -43,12 +44,12 @@ func ReadResourceChanges(data []byte) ([]*tfjson.ResourceChange, error) {
 	plan := tfjson.Plan{}
 	err := plan.UnmarshalJSON(data)
 	if err != nil {
-		return nil, errors.Wrap(err, "reading JSON plan")
+		return nil, fmt.Errorf("reading JSON plan: %w", err)
 	}
 
 	err = plan.Validate()
 	if err != nil {
-		return nil, errors.Wrap(err, "validating JSON plan")
+		return nil, fmt.Errorf("validating JSON plan: %w", err)
 	}
 
 	return plan.ResourceChanges, nil


Test patch:
diff --git a/ancestrymanager/ancestrymanager_test.go b/ancestrymanager/ancestrymanager_test.go
index 5bcefe41..e971ad9d 100644
--- a/ancestrymanager/ancestrymanager_test.go
+++ b/ancestrymanager/ancestrymanager_test.go
@@ -629,6 +629,23 @@ func TestGetAncestors_Folder(t *testing.T) {
 			v1Count: 1,
 			v3Count: 1,
 		},
+		{
+			name: "folder ID is empty string and no ancestor",
+			data: tfdata.NewFakeResourceData(
+				"google_folder_iam_member",
+				p.ResourcesMap["google_folder_iam_member"].Schema,
+				map[string]interface{}{
+					"folder": "",
+				},
+			),
+			asset: &resources.Asset{
+				Type: "cloudresourcemanager.googleapis.com/Folder",
+			},
+			v3Responses: map[string]*crmv3.Project{},
+			v1Responses: map[string][]*crmv1.Ancestor{},
+			want:        []string{"organizations/unknown"},
+			parent:      "//cloudresourcemanager.googleapis.com/organizations/unknown",
+		},
 	}
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
diff --git a/ancestrymanager/ancestryutil_test.go b/ancestrymanager/ancestryutil_test.go
index 3a09af30..1cd46365 100644
--- a/ancestrymanager/ancestryutil_test.go
+++ b/ancestrymanager/ancestryutil_test.go
@@ -40,6 +40,12 @@ func TestAssetParent(t *testing.T) {
 			cai:       &resources.Asset{Type: "cloudresourcemanager.googleapis.com/Folder"},
 			want:      "//cloudresourcemanager.googleapis.com/organizations/789",
 		},
+		{
+			name:      "new folder with unknown org",
+			ancestors: []string{"organizations/unknown"},
+			cai:       &resources.Asset{Type: "cloudresourcemanager.googleapis.com/Folder"},
+			want:      "//cloudresourcemanager.googleapis.com/organizations/unknown",
+		},
 		{
 			name:      "organization",
 			ancestors: []string{"organizations/789"},
diff --git a/converters/google/convert_test.go b/converters/google/convert_test.go
index ea5b676f..d481c73b 100644
--- a/converters/google/convert_test.go
+++ b/converters/google/convert_test.go
@@ -17,6 +17,7 @@ package google
 import (
 	"bytes"
 	"context"
+	"fmt"
 	"sort"
 	"strings"
 	"testing"
@@ -27,7 +28,6 @@ import (
 	"github.com/GoogleCloudPlatform/terraform-validator/tfdata"
 	tfjson "github.com/hashicorp/terraform-json"
 	provider "github.com/hashicorp/terraform-provider-google/google"
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
@@ -67,7 +67,7 @@ func newTestConverter(convertUnchanged bool) (*Converter, *bytes.Buffer, error)
 	offline := true
 	cfg, err := resources.NewConfig(ctx, project, "", "", offline, "", nil)
 	if err != nil {
-		return nil, nil, errors.Wrap(err, "constructing configuration")
+		return nil, nil, fmt.Errorf("constructing configuration: %w", err)
 	}
 	errorLogger, buf := newTestErrorLogger()
 	c := NewConverter(cfg, &ancestrymanager.NoOpAncestryManager{}, offline, convertUnchanged, errorLogger)
diff --git a/test/cli_test.go b/test/cli_test.go
index 91aed123..cabdc73e 100644
--- a/test/cli_test.go
+++ b/test/cli_test.go
@@ -102,6 +102,7 @@ func TestCLI(t *testing.T) {
 		{name: "example_dns_managed_zone"},
 		{name: "example_dns_policy"},
 		{name: "example_filestore_instance"},
+		{name: "example_folder_iam_member_empty_folder"},
 		{name: "example_folder_iam_policy"},
 		{name: "example_folder_organization_policy"},
 		{name: "example_google_cloudfunctions_function"},
diff --git a/test/read_test.go b/test/read_test.go
index 8a7ee50e..ed317a56 100644
--- a/test/read_test.go
+++ b/test/read_test.go
@@ -64,6 +64,7 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 		{name: "example_dns_managed_zone"},
 		{name: "example_dns_policy"},
 		{name: "example_filestore_instance"},
+		{name: "example_folder_iam_member_empty_folder"},
 		{name: "example_folder_iam_policy"},
 		{name: "example_folder_organization_policy"},
 		{name: "example_google_cloudfunctions_function"},
diff --git a/testdata/templates/example_folder_iam_member_empty_folder.json b/testdata/templates/example_folder_iam_member_empty_folder.json
new file mode 100644
index 00000000..73c93c42
--- /dev/null
+++ b/testdata/templates/example_folder_iam_member_empty_folder.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "//cloudresourcemanager.googleapis.com/placeholder-c2WD8F2q",
+    "asset_type": "cloudresourcemanager.googleapis.com/Folder",
+    "ancestry_path": "organization/unknown",
+    "iam_policy": {
+      "bindings": [
+        {
+          "role": "roles/editor",
+          "members": [
+            "user:jane@example.com"
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/testdata/templates/example_folder_iam_member_empty_folder.tf b/testdata/templates/example_folder_iam_member_empty_folder.tf
new file mode 100644
index 00000000..22ad9421
--- /dev/null
+++ b/testdata/templates/example_folder_iam_member_empty_folder.tf
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2022 Google LLC
+ *
+ * Licensed 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.
+ */
+
+terraform {
+  required_providers {
+    google = {
+      source = "hashicorp/google"
+      version = "~> {{.Provider.version}}"
+    }
+  }
+}
+
+provider "google" {
+  {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}}
+}
+
+resource "random_string" "suffix" {
+  length  = 4
+  upper   = false
+  special = false
+}
+
+resource "google_folder_iam_member" "editor" {
+  folder  = "${random_string.suffix.result}"
+  role    = "roles/editor"
+  member  = "user:jane@example.com"
+}
diff --git a/testdata/templates/example_folder_iam_member_empty_folder.tfplan.json b/testdata/templates/example_folder_iam_member_empty_folder.tfplan.json
new file mode 100644
index 00000000..5009d7bd
--- /dev/null
+++ b/testdata/templates/example_folder_iam_member_empty_folder.tfplan.json
@@ -0,0 +1,157 @@
+{
+  "format_version": "0.2",
+  "terraform_version": "1.0.10",
+  "planned_values": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_folder_iam_member.editor",
+          "mode": "managed",
+          "type": "google_folder_iam_member",
+          "name": "editor",
+          "provider_name": "registry.terraform.io/hashicorp/google",
+          "schema_version": 0,
+          "values": {
+            "condition": [],
+            "member": "user:jane@example.com",
+            "role": "roles/editor"
+          },
+          "sensitive_values": {
+            "condition": []
+          }
+        },
+        {
+          "address": "random_string.suffix",
+          "mode": "managed",
+          "type": "random_string",
+          "name": "suffix",
+          "provider_name": "registry.terraform.io/hashicorp/random",
+          "schema_version": 1,
+          "values": {
+            "keepers": null,
+            "length": 4,
+            "lower": true,
+            "min_lower": 0,
+            "min_numeric": 0,
+            "min_special": 0,
+            "min_upper": 0,
+            "number": true,
+            "override_special": null,
+            "special": false,
+            "upper": false
+          },
+          "sensitive_values": {}
+        }
+      ]
+    }
+  },
+  "resource_changes": [
+    {
+      "address": "google_folder_iam_member.editor",
+      "mode": "managed",
+      "type": "google_folder_iam_member",
+      "name": "editor",
+      "provider_name": "registry.terraform.io/hashicorp/google",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "condition": [],
+          "member": "user:jane@example.com",
+          "role": "roles/editor"
+        },
+        "after_unknown": {
+          "condition": [],
+          "etag": true,
+          "folder": true,
+          "id": true
+        },
+        "before_sensitive": false,
+        "after_sensitive": {
+          "condition": []
+        }
+      }
+    },
+    {
+      "address": "random_string.suffix",
+      "mode": "managed",
+      "type": "random_string",
+      "name": "suffix",
+      "provider_name": "registry.terraform.io/hashicorp/random",
+      "change": {
+        "actions": [
+          "create"
+        ],
+        "before": null,
+        "after": {
+          "keepers": null,
+          "length": 4,
+          "lower": true,
+          "min_lower": 0,
+          "min_numeric": 0,
+          "min_special": 0,
+          "min_upper": 0,
+          "number": true,
+          "override_special": null,
+          "special": false,
+          "upper": false
+        },
+        "after_unknown": {
+          "id": true,
+          "result": true
+        },
+        "before_sensitive": false,
+        "after_sensitive": {}
+      }
+    }
+  ],
+  "configuration": {
+    "root_module": {
+      "resources": [
+        {
+          "address": "google_folder_iam_member.editor",
+          "mode": "managed",
+          "type": "google_folder_iam_member",
+          "name": "editor",
+          "provider_config_key": "google",
+          "expressions": {
+            "folder": {
+              "references": [
+                "random_string.suffix.result",
+                "random_string.suffix"
+              ]
+            },
+            "member": {
+              "constant_value": "user:jane@example.com"
+            },
+            "role": {
+              "constant_value": "roles/editor"
+            }
+          },
+          "schema_version": 0
+        },
+        {
+          "address": "random_string.suffix",
+          "mode": "managed",
+          "type": "random_string",
+          "name": "suffix",
+          "provider_config_key": "random",
+          "expressions": {
+            "length": {
+              "constant_value": 4
+            },
+            "special": {
+              "constant_value": false
+            },
+            "upper": {
+              "constant_value": false
+            }
+          },
+          "schema_version": 1
+        }
+      ]
+    }
+  }
+}