Repository: GoogleCloudPlatform/terraform-validator
Pull request #718 (2022-05-24 16:41:42)

Add user agent parameter to ReadPlannedAssets()
This change allows other libraries to call `ReadPlannedAssets()` with an application-specific user-agent. The terraform-validator convert and validate commands will default to `config-validator-tf/*`.

Note that this also deprecates the usage of `GOOGLE_TERRAFORM_VALIDATOR_USERAGENT_EXTENSION` to append to the user-agent.


Patch:
diff --git a/cmd/convert.go b/cmd/convert.go
index a7b04b39..83c1c8da 100644
--- a/cmd/convert.go
+++ b/cmd/convert.go
@@ -17,9 +17,11 @@ package cmd
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"os"
 
 	"github.com/GoogleCloudPlatform/terraform-validator/tfgcv"
+	"github.com/GoogleCloudPlatform/terraform-validator/version"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	"go.uber.org/zap"
@@ -96,7 +98,8 @@ func (o *convertOptions) run(plan string) error {
 	ancestryCache := map[string]string{
 		o.project: o.ancestry,
 	}
-	assets, err := o.readPlannedAssets(ctx, plan, o.project, ancestryCache, o.offline, false, o.rootOptions.errorLogger)
+	userAgent := fmt.Sprintf("config-validator-tf/%s", version.BuildVersion())
+	assets, err := o.readPlannedAssets(ctx, plan, o.project, ancestryCache, o.offline, false, o.rootOptions.errorLogger, userAgent)
 	if err != nil {
 		if errors.Cause(err) == tfgcv.ErrParsingProviderProject {
 			return errors.New("unable to parse provider project, please use --project flag")
diff --git a/cmd/validate.go b/cmd/validate.go
index cb48fb75..33758fcb 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -24,6 +24,7 @@ import (
 	"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
 	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
 	"github.com/GoogleCloudPlatform/terraform-validator/tfgcv"
+	"github.com/GoogleCloudPlatform/terraform-validator/version"
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
@@ -115,7 +116,8 @@ func (o *validateOptions) run(plan string) error {
 		ancestryCache := map[string]string{
 			o.project: o.ancestry,
 		}
-		assets, err = o.readPlannedAssets(ctx, plan, o.project, ancestryCache, o.offline, false, o.rootOptions.errorLogger)
+		userAgent := fmt.Sprintf("config-validator-tf/%s", version.BuildVersion())
+		assets, err = o.readPlannedAssets(ctx, plan, o.project, ancestryCache, o.offline, false, o.rootOptions.errorLogger, userAgent)
 		if err != nil {
 			if errors.Cause(err) == tfgcv.ErrParsingProviderProject {
 				return errors.New("unable to parse provider project, please use --project flag")
diff --git a/converters/google/resources/config.go b/converters/google/resources/config.go
index efae8f6b..2461dbab 100644
--- a/converters/google/resources/config.go
+++ b/converters/google/resources/config.go
@@ -251,6 +251,7 @@ type Config struct {
 	CloudResourceManagerBasePath string
 	ContainerAwsBasePath         string
 	ContainerAzureBasePath       string
+	DataplexBasePath             string
 	EventarcBasePath             string
 	FirebaserulesBasePath        string
 	GkeHubBasePath               string
diff --git a/converters/google/resources/getconfig.go b/converters/google/resources/getconfig.go
index 88383ba2..106aa5e1 100644
--- a/converters/google/resources/getconfig.go
+++ b/converters/google/resources/getconfig.go
@@ -2,12 +2,8 @@ package google
 
 import (
 	"context"
-	"fmt"
-	"os"
 
 	"github.com/pkg/errors"
-
-	"github.com/GoogleCloudPlatform/terraform-validator/version"
 )
 
 // Return the value of the private userAgent field
@@ -15,10 +11,10 @@ func (c *Config) UserAgent() string {
 	return c.userAgent
 }
 
-func GetConfig(ctx context.Context, project string, offline bool) (*Config, error) {
+func GetConfig(ctx context.Context, project string, offline bool, userAgent string) (*Config, error) {
 	cfg := &Config{
 		Project:   project,
-		userAgent: fmt.Sprintf("config-validator-tf/%s", version.BuildVersion()),
+		userAgent: userAgent,
 	}
 
 	// Search for default credentials
@@ -48,12 +44,6 @@ func GetConfig(ctx context.Context, project string, offline bool) (*Config, erro
 		"CLOUDSDK_COMPUTE_REGION",
 	})
 
-	// opt in extension for adding to the User-Agent header
-	if ext := os.Getenv("GOOGLE_TERRAFORM_VALIDATOR_USERAGENT_EXTENSION"); ext != "" {
-		ua := cfg.userAgent
-		cfg.userAgent = fmt.Sprintf("%s %s", ua, ext)
-	}
-
 	if !offline {
 		ConfigureBasePaths(cfg)
 		if err := cfg.LoadAndValidate(ctx); err != nil {
diff --git a/go.mod b/go.mod
index 5be50afe..b53243ff 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/hashicorp/go-cleanhttp v0.5.2
 	github.com/hashicorp/terraform-json v0.13.0
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
-	github.com/hashicorp/terraform-provider-google v1.20.1-0.20220524181418-2099a4266ab1
+	github.com/hashicorp/terraform-provider-google v1.20.1-0.20220525180115-2c39a0446a11
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/onsi/gomega v1.17.0 // indirect
diff --git a/go.sum b/go.sum
index f37fb87d..46a7e32a 100644
--- a/go.sum
+++ b/go.sum
@@ -703,8 +703,8 @@ github.com/hashicorp/terraform-plugin-log v0.2.0 h1:rjflRuBqCnSk3UHOR25MP1G5BDLK
 github.com/hashicorp/terraform-plugin-log v0.2.0/go.mod h1:E1kJmapEHzqu1x6M++gjvhzM2yMQNXPVWZRCB8sgYjg=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 h1:B9AocC+dxrCqcf4vVhztIkSkt3gpRjUkEka8AmZWGlQ=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1/go.mod h1:FjM9DXWfP0w/AeOtJoSKHBZ01LqmaO6uP4bXhv3fekw=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20220524181418-2099a4266ab1 h1:muhb9p6slwWdcOQdi+1BNw9sYB2fnGLKdFWpwD/90qo=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20220524181418-2099a4266ab1/go.mod h1:j3C317ieF2X8MaHzA3jCdIL1DlAQsu9Hw/f09olZ+a4=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20220525180115-2c39a0446a11 h1:zp032ynfPXJmi6vm7jILJc9elQXzctX/hwFPeX91TmU=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20220525180115-2c39a0446a11/go.mod h1:j3C317ieF2X8MaHzA3jCdIL1DlAQsu9Hw/f09olZ+a4=
 github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw=
 github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co=
 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 99da7d10..4b5332ca 100644
--- a/tfgcv/planned_assets.go
+++ b/tfgcv/planned_assets.go
@@ -30,7 +30,7 @@ import (
 	"go.uber.org/zap"
 )
 
-type ReadPlannedAssetsFunc func(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error)
+type ReadPlannedAssetsFunc func(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger, userAgent string) ([]google.Asset, error)
 
 // ReadPlannedAssets extracts CAI assets from a terraform plan file.
 // If ancestry path is provided, it assumes the project is in that path rather
@@ -39,8 +39,8 @@ type ReadPlannedAssetsFunc func(ctx context.Context, path, project string, ances
 // are also reported in the output, otherwise only resources that are going to
 // be changed are reported.
 // It ignores non-supported resources.
-func ReadPlannedAssets(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
-	converter, err := newConverter(ctx, path, project, ancestry, offline, convertUnchanged, errorLogger)
+func ReadPlannedAssets(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger, userAgent string) ([]google.Asset, error) {
+	converter, err := newConverter(ctx, path, project, ancestry, offline, convertUnchanged, errorLogger, userAgent)
 	if err != nil {
 		return nil, err
 	}
@@ -63,8 +63,8 @@ func ReadPlannedAssets(ctx context.Context, path, project string, ancestry map[s
 	return converter.Assets(), nil
 }
 
-func newConverter(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) (*google.Converter, error) {
-	cfg, err := resources.GetConfig(ctx, project, offline)
+func newConverter(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger, userAgent string) (*google.Converter, error) {
+	cfg, err := resources.GetConfig(ctx, project, offline, userAgent)
 	if err != nil {
 		return nil, errors.Wrap(err, "building google configuration")
 	}


Test patch:
diff --git a/cmd/convert_test.go b/cmd/convert_test.go
index 54e7db9e..e4b116c3 100644
--- a/cmd/convert_test.go
+++ b/cmd/convert_test.go
@@ -36,7 +36,7 @@ func testAssets() []google.Asset {
 	}
 }
 
-func MockReadPlannedAssets(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger) ([]google.Asset, error) {
+func MockReadPlannedAssets(ctx context.Context, path, project string, ancestry map[string]string, offline, convertUnchanged bool, errorLogger *zap.Logger, userAgent string) ([]google.Asset, error) {
 	return testAssets(), nil
 }
 
diff --git a/converters/google/convert_test.go b/converters/google/convert_test.go
index a8f1dd54..4ab832b5 100644
--- a/converters/google/convert_test.go
+++ b/converters/google/convert_test.go
@@ -34,7 +34,7 @@ func newTestConverter(convertUnchanged bool) (*Converter, error) {
 	ctx := context.Background()
 	project := testProject
 	offline := true
-	cfg, err := resources.GetConfig(ctx, project, offline)
+	cfg, err := resources.GetConfig(ctx, project, offline, "")
 	if err != nil {
 		return nil, errors.Wrap(err, "constructing configuration")
 	}
diff --git a/converters/google/resources/getconfig_test.go b/converters/google/resources/getconfig_test.go
index 3e17c7d6..a776984e 100644
--- a/converters/google/resources/getconfig_test.go
+++ b/converters/google/resources/getconfig_test.go
@@ -116,13 +116,6 @@ func TestGetConfigExtractsEnvVars(t *testing.T) {
 			expected:       "whatever",
 			getConfigValue: getRegionValue,
 		},
-		{
-			name:           "GOOGLE_TERRAFORM_VALIDATOR_USERAGENT_EXTENSION",
-			envKey:         "GOOGLE_TERRAFORM_VALIDATOR_USERAGENT_EXTENSION",
-			envValue:       "whatever",
-			expected:       "config-validator-tf/dev whatever",
-			getConfigValue: getUserAgent,
-		},
 	}
 
 	for _, c := range cases {
@@ -133,7 +126,7 @@ func TestGetConfigExtractsEnvVars(t *testing.T) {
 				t.Fatalf("error setting env var %s=%s: %s", c.envKey, c.envValue, err)
 			}
 
-			cfg, err := GetConfig(ctx, "project", offline)
+			cfg, err := GetConfig(ctx, "project", offline, "")
 			if err != nil {
 				t.Fatalf("error building converter: %s", err)
 			}
@@ -154,3 +147,32 @@ func TestGetConfigExtractsEnvVars(t *testing.T) {
 		})
 	}
 }
+
+func TestGetConfigUserAgent(t *testing.T) {
+	ctx := context.Background()
+	offline := true
+	cases := []struct {
+		userAgent string
+		expected  string
+	}{
+		{
+			userAgent: "",
+			expected:  "",
+		},
+		{
+			userAgent: "whatever",
+			expected:  "whatever",
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.userAgent, func(t *testing.T) {
+			cfg, err := GetConfig(ctx, "project", offline, c.userAgent)
+			if err != nil {
+				t.Fatalf("error building converter: %s", err)
+			}
+
+			assert.Equal(t, c.expected, getUserAgent(cfg))
+		})
+	}
+}
diff --git a/test/read_test.go b/test/read_test.go
index 865b0679..9619f603 100644
--- a/test/read_test.go
+++ b/test/read_test.go
@@ -143,7 +143,7 @@ func TestReadPlannedAssetsCoverage(t *testing.T) {
 			ancestryCache := map[string]string{
 				data.Provider["project"]: data.Ancestry,
 			}
-			got, err := tfgcv.ReadPlannedAssets(ctx, planfile, data.Provider["project"], ancestryCache, true, false, zaptest.NewLogger(t))
+			got, err := tfgcv.ReadPlannedAssets(ctx, planfile, data.Provider["project"], ancestryCache, true, false, zaptest.NewLogger(t), "")
 			if err != nil {
 				t.Fatalf("ReadPlannedAssets(%s, %s, %s, %t): %v", planfile, data.Provider["project"], ancestryCache, true, err)
 			}
diff --git a/tfgcv/planned_assets_test.go b/tfgcv/planned_assets_test.go
index d6fc3a55..38caa7e7 100644
--- a/tfgcv/planned_assets_test.go
+++ b/tfgcv/planned_assets_test.go
@@ -80,7 +80,7 @@ func TestReadPlannedAssets(t *testing.T) {
 			ancestryCache := map[string]string{
 				tt.args.project: tt.args.ancestry,
 			}
-			got, err := ReadPlannedAssets(ctx, testFile, tt.args.project, ancestryCache, offline, tt.args.convertUnchanged, zap.NewExample())
+			got, err := ReadPlannedAssets(ctx, testFile, tt.args.project, ancestryCache, offline, tt.args.convertUnchanged, zap.NewExample(), "")
 			if (err != nil) != tt.wantErr {
 				t.Errorf("ReadPlannedAssets() error = %v, wantErr %v", err, tt.wantErr)
 				return