Repository: GoogleCloudPlatform/terraform-validator
Pull request #461 (2022-01-11 16:54:33)

Log level after gcloud change
This is to address https://github.com/GoogleCloudPlatform/terraform-validator/issues/372

gcloud need to be changed for structured output schema to accept log level first, else the schema validation complains about the new field. The change is released with version 368.0. 

Now that with the change, we can encode the level key in both structured logging and non structured logging. 

Tested with the latest release of gcloud 368.0 version, and tested with such a command 
```
$ gcloud alpha resource-config validate terraform /home/ciris/test-tf/tfplan.json --policy-library=~/test-tf/policy-library1 --verbosity info
```
- with the default terraform-validator binary (the one downloaded by gcloud)
```
ERROR: Error: [[INFO] Authenticating using configured Google JSON 'access_token'...].
ERROR: Error: [[INFO]   -- Scopes: [https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email]].
ERROR: Error: [[INFO] Authenticating using configured Google JSON 'access_token'...].
...
```
- with the compiled binary with this change,
```
ERROR: Info: [[INFO] Authenticating using configured Google JSON 'access_token'...].
ERROR: Info: [[INFO]   -- Scopes: [https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email]].
ERROR: Info: [[INFO] Authenticating using configured Google JSON 'access_token'...].
...
```

It shows with the change, it is able to resolve the log level. The starting `ERROR:` is prefixed by gcloud. 


ScottSuarez: Could you explain these changes in more detail?

Patch:
diff --git a/cmd/logger.go b/cmd/logger.go
index b682647a..27dcd9e7 100644
--- a/cmd/logger.go
+++ b/cmd/logger.go
@@ -99,7 +99,7 @@ func newErrorLogger(verbosity string, useStructuredLogging bool, writeSyncer zap
 
 	encoderConfig := zapcore.EncoderConfig{
 		TimeKey:       "timestamp",
-		LevelKey:      "",
+		LevelKey:      "level",
 		MessageKey:    "",
 		StacktraceKey: "",
 		EncodeLevel:   zapcore.LowercaseLevelEncoder,
@@ -109,7 +109,6 @@ func newErrorLogger(verbosity string, useStructuredLogging bool, writeSyncer zap
 	if useStructuredLogging {
 		encoder = newJSONEncoder(encoderConfig)
 	} else {
-		encoderConfig.LevelKey = "level"
 		encoder = newConsoleEncoder(encoderConfig)
 	}
 	core := zapcore.NewCore(encoder, writeSyncer, level)
diff --git a/converters/google/resources/compute_reservation.go b/converters/google/resources/compute_reservation.go
index 33f90675..d1be7352 100644
--- a/converters/google/resources/compute_reservation.go
+++ b/converters/google/resources/compute_reservation.go
@@ -17,6 +17,8 @@ package google
 import (
 	"fmt"
 	"reflect"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 )
 
 const ComputeReservationAssetType string = "compute.googleapis.com/Reservation"
@@ -69,6 +71,12 @@ func GetComputeReservationApiObject(d TerraformResourceData, config *Config) (ma
 	} else if v, ok := d.GetOkExists("specific_reservation_required"); !isEmptyValue(reflect.ValueOf(specificReservationRequiredProp)) && (ok || !reflect.DeepEqual(v, specificReservationRequiredProp)) {
 		obj["specificReservationRequired"] = specificReservationRequiredProp
 	}
+	shareSettingsProp, err := expandComputeReservationShareSettings(d.Get("share_settings"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("share_settings"); !isEmptyValue(reflect.ValueOf(shareSettingsProp)) && (ok || !reflect.DeepEqual(v, shareSettingsProp)) {
+		obj["shareSettings"] = shareSettingsProp
+	}
 	specificReservationProp, err := expandComputeReservationSpecificReservation(d.Get("specific_reservation"), d, config)
 	if err != nil {
 		return nil, err
@@ -97,6 +105,65 @@ func expandComputeReservationSpecificReservationRequired(v interface{}, d Terraf
 	return v, nil
 }
 
+func expandComputeReservationShareSettings(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{})
+
+	transformedShareType, err := expandComputeReservationShareSettingsShareType(original["share_type"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedShareType); val.IsValid() && !isEmptyValue(val) {
+		transformed["shareType"] = transformedShareType
+	}
+
+	transformedProjectMap, err := expandComputeReservationShareSettingsProjectMap(original["project_map"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedProjectMap); val.IsValid() && !isEmptyValue(val) {
+		transformed["projectMap"] = transformedProjectMap
+	}
+
+	return transformed, nil
+}
+
+func expandComputeReservationShareSettingsShareType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandComputeReservationShareSettingsProjectMap(v interface{}, d TerraformResourceData, config *Config) (map[string]interface{}, error) {
+	if v == nil {
+		return map[string]interface{}{}, nil
+	}
+	m := make(map[string]interface{})
+	for _, raw := range v.(*schema.Set).List() {
+		original := raw.(map[string]interface{})
+		transformed := make(map[string]interface{})
+
+		transformedProjectId, err := expandComputeReservationShareSettingsProjectMapProjectId(original["project_id"], d, config)
+		if err != nil {
+			return nil, err
+		} else if val := reflect.ValueOf(transformedProjectId); val.IsValid() && !isEmptyValue(val) {
+			transformed["projectId"] = transformedProjectId
+		}
+
+		transformedId, err := expandString(original["id"], d, config)
+		if err != nil {
+			return nil, err
+		}
+		m[transformedId] = transformed
+	}
+	return m, nil
+}
+
+func expandComputeReservationShareSettingsProjectMapProjectId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
 func expandComputeReservationSpecificReservation(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
 	l := v.([]interface{})
 	if len(l) == 0 || l[0] == nil {


Test patch:
diff --git a/cmd/logger_test.go b/cmd/logger_test.go
index bfb23003..191cdeb6 100644
--- a/cmd/logger_test.go
+++ b/cmd/logger_test.go
@@ -56,6 +56,7 @@ func TestErrorLoggerSchema(t *testing.T) {
 	expectedOutput := map[string]interface{}{
 		"version":   "v1.0.0",
 		"timestamp": "tested separately",
+		"level":     "info",
 		"error_details": map[string]interface{}{
 			"error":   "This is a message",
 			"context": "",