Skip to content

Commit

Permalink
[Phase 2] Include Auth Login blocks in multiplexed Provider configura…
Browse files Browse the repository at this point in the history
…tion (#2097)

make updates to all auth logins and fix build

run go mod tidy

register all auth logins to test build

add all auth login blocks and match schema implementations

set default for azure scope

address todos

- remove commented code; neither are read from env vars
- add validators
  • Loading branch information
vinay-gopalan authored Nov 29, 2023
1 parent 78f1c0c commit 289a101
Show file tree
Hide file tree
Showing 32 changed files with 1,012 additions and 113 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/hashicorp/terraform-plugin-framework v1.4.1
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
github.com/hashicorp/terraform-plugin-go v0.19.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.12.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0
github.com/hashicorp/vault v1.11.3
Expand Down Expand Up @@ -134,7 +135,6 @@ require (
github.com/hashicorp/serf v0.9.7 // indirect
github.com/hashicorp/terraform-exec v0.19.0 // indirect
github.com/hashicorp/terraform-json v0.17.1 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.2 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
Expand Down
3 changes: 3 additions & 0 deletions internal/framework/validators/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Terraform Plugin Framework Validators

This package contains custom Terraform Plugin Framework [validators](https://developer.hashicorp.com/terraform/plugin/framework/validation).
49 changes: 49 additions & 0 deletions internal/framework/validators/gcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validators

import (
"context"
"os"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
googleoauth "golang.org/x/oauth2/google"
)

// Credentials Validator
var _ validator.String = credentialsValidator{}

// credentialsValidator validates that a string Attribute's is valid JSON credentials.
type credentialsValidator struct{}

// Description describes the validation in plain text formatting.
func (v credentialsValidator) Description(_ context.Context) string {
return "value must be a path to valid JSON credentials or valid, raw, JSON credentials"
}

// MarkdownDescription describes the validation in Markdown formatting.
func (v credentialsValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

// ValidateString performs the validation.
func (v credentialsValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) {
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
return
}

value := request.ConfigValue.ValueString()

// if this is a path and we can stat it, assume it's ok
if _, err := os.Stat(value); err == nil {
return
}
if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(value)); err != nil {
response.Diagnostics.AddError("JSON credentials are not valid", err.Error())
}
}

func GCPCredentialsValidator() validator.String {
return credentialsValidator{}
}
83 changes: 83 additions & 0 deletions internal/framework/validators/gcp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validators

import (
"context"
"io/ioutil"
"testing"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

const testFakeCredentialsPath = "./test-fixtures/fake_account.json"

func TestFrameworkProvider_CredentialsValidator(t *testing.T) {
cases := map[string]struct {
ConfigValue func(t *testing.T) types.String
ExpectedWarningCount int
ExpectedErrorCount int
}{
"configuring credentials as a path to a credentials JSON file is valid": {
ConfigValue: func(t *testing.T) types.String {
return types.StringValue(testFakeCredentialsPath) // Path to a test fixture
},
},
"configuring credentials as a path to a non-existant file is NOT valid": {
ConfigValue: func(t *testing.T) types.String {
return types.StringValue("./this/path/doesnt/exist.json") // Doesn't exist
},
ExpectedErrorCount: 1,
},
"configuring credentials as a credentials JSON string is valid": {
ConfigValue: func(t *testing.T) types.String {
contents, err := ioutil.ReadFile(testFakeCredentialsPath)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
stringContents := string(contents)
return types.StringValue(stringContents)
},
},
"configuring credentials as an empty string is not valid": {
ConfigValue: func(t *testing.T) types.String {
return types.StringValue("")
},
ExpectedErrorCount: 1,
},
"leaving credentials unconfigured is valid": {
ConfigValue: func(t *testing.T) types.String {
return types.StringNull()
},
},
}

for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
// Arrange
req := validator.StringRequest{
ConfigValue: tc.ConfigValue(t),
}

resp := validator.StringResponse{
Diagnostics: diag.Diagnostics{},
}

cv := GCPCredentialsValidator()

// Act
cv.ValidateString(context.Background(), req, &resp)

// Assert
if resp.Diagnostics.WarningsCount() > tc.ExpectedWarningCount {
t.Errorf("Expected %d warnings, got %d", tc.ExpectedWarningCount, resp.Diagnostics.WarningsCount())
}
if resp.Diagnostics.ErrorsCount() > tc.ExpectedErrorCount {
t.Errorf("Expected %d errors, got %d", tc.ExpectedErrorCount, resp.Diagnostics.ErrorsCount())
}
})
}
}
7 changes: 7 additions & 0 deletions internal/framework/validators/test-fixtures/fake_account.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private_key_id": "foo",
"private_key": "bar",
"client_email": "[email protected]",
"client_id": "[email protected]",
"type": "service_account"
}
36 changes: 17 additions & 19 deletions internal/provider/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
type (
loginSchemaFunc func(string) *schema.Schema
getSchemaResource func(string) *schema.Resource
validateFunc func(data *schema.ResourceData) error
validateFunc func(data *schema.ResourceData, params map[string]interface{}) error
authLoginFunc func(*schema.ResourceData) (AuthLogin, error)
)

Expand Down Expand Up @@ -54,11 +54,6 @@ type authLoginRegistry struct {
// Register field for loginFunc and schemaFunc. A field can only be registered
// once.
func (r *authLoginRegistry) Register(field string, loginFunc authLoginFunc, schemaFunc loginSchemaFunc) error {
// TODO(JM): remove this after testing
// for now we only register auth_login_token_file
if field != consts.FieldAuthLoginTokenFile {
return nil
}
e := &authLoginEntry{
field: field,
loginFunc: loginFunc,
Expand Down Expand Up @@ -143,15 +138,15 @@ func (l *AuthLoginCommon) Init(d *schema.ResourceData, authField string, validat
return err
}

l.mount = path
l.params = params

for _, vf := range validators {
if err := vf(d); err != nil {
if err := vf(d, params); err != nil {
return err
}
}

l.mount = path
l.params = params

return l.validate()
}

Expand Down Expand Up @@ -278,34 +273,37 @@ func (l *AuthLoginCommon) init(d *schema.ResourceData) (string, map[string]inter

type authDefault struct {
field string
envVar string
envVars []string
defaultVal string
}

type authDefaults []authDefault

func (l *AuthLoginCommon) setDefaultFields(d *schema.ResourceData, defaults authDefaults) error {
func (l *AuthLoginCommon) setDefaultFields(d *schema.ResourceData, defaults authDefaults, params map[string]interface{}) error {
for _, f := range defaults {
if _, ok := l.getOk(d, f.field); !ok {

// if field is unset in the config, check env
defaultValue := os.Getenv(f.envVar)
if defaultValue == "" {
defaultValue = f.defaultVal
for _, envVar := range f.envVars {
defaultValue := os.Getenv(envVar)
if defaultValue == "" {
defaultValue = f.defaultVal
}
params[f.field] = defaultValue
// found a value, no need to check other options
break
}
l.params[f.field] = defaultValue
}
}

return nil
}

func (l *AuthLoginCommon) checkRequiredFields(d *schema.ResourceData, required ...string) error {
func (l *AuthLoginCommon) checkRequiredFields(d *schema.ResourceData, params map[string]interface{}, required ...string) error {
var missing []string
for _, f := range required {
if data, ok := l.getOk(d, f); !ok {
// if the field was unset in the config
if l.params[f] == data {
if params[f] == data {
missing = append(missing, f)
}
}
Expand Down
Loading

0 comments on commit 289a101

Please sign in to comment.