Skip to content

Commit

Permalink
[Phase 1] Multiplex provider - minimal TF Plugin Framework provider i…
Browse files Browse the repository at this point in the history
…mplementation (#2067)

* implement the framework provider

* setup provider server factory

* setup framework provider schema

* handle defaults in configure

* [Phase 2] Complete the provider configuration setup (#2090)

* setup framework provider schema

* handle defaults in configure

* WIP: handle auth login methods

* [Phase 2] Include Auth Login blocks in multiplexed Provider configuration (#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

* remove set_namespace_from_token field

* add custom path validator

* configure namespace for vault client

* review comments

* add muxed provider acc tests

* use consts, set required fields and update tests

* add tests for multi envs and fix bug

* fix bug with overriding config val

* add custom URI validator for OIDC auth

* fix a few default mount types

* add comments and migration state example acc test

* remove dupe default entries; add validators

* remove client_auth docs

* add kerbos token and file path validators

* don't use double pointer and remove redundant return

---------

Co-authored-by: vinay-gopalan <[email protected]>

---------

Co-authored-by: vinay-gopalan <[email protected]>
  • Loading branch information
fairclothjm and vinay-gopalan authored Dec 12, 2023
1 parent 6125fee commit 3ddd03e
Show file tree
Hide file tree
Showing 57 changed files with 2,616 additions and 288 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ require (
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7
github.com/hashicorp/go-version v1.6.0
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 @@ -132,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
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,10 @@ github.com/hashicorp/terraform-exec v0.19.0 h1:FpqZ6n50Tk95mItTSS9BjeOVUb4eg81Sp
github.com/hashicorp/terraform-exec v0.19.0/go.mod h1:tbxUpe3JKruE9Cuf65mycSIT8KiNPZ0FkuTE3H4urQg=
github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQHgyRwf3RkyA=
github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o=
github.com/hashicorp/terraform-plugin-framework v1.4.1 h1:ZC29MoB3Nbov6axHdgPbMz7799pT5H8kIrM8YAsaVrs=
github.com/hashicorp/terraform-plugin-framework v1.4.1/go.mod h1:XC0hPcQbBvlbxwmjxuV/8sn8SbZRg4XwGMs22f+kqV0=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU=
github.com/hashicorp/terraform-plugin-go v0.19.0/go.mod h1:EhRSkEPNoylLQntYsk5KrDHTZJh9HQoumZXbOGOXmec=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
Expand Down
3 changes: 0 additions & 3 deletions internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ const (
FieldUsername = "username"
FieldPassword = "password"
FieldPasswordFile = "password_file"
FieldClientAuth = "client_auth"
FieldAuthLoginGeneric = "auth_login"
FieldAuthLoginUserpass = "auth_login_userpass"
FieldAuthLoginAWS = "auth_login_aws"
Expand Down Expand Up @@ -363,12 +362,10 @@ const (
FieldServiceAccountJWT = "service_account_jwt"
FieldDisableISSValidation = "disable_iss_validation"
FieldPEMKeys = "pem_keys"
FieldSetNamespaceFromToken = "set_namespace_from_token"
/*
common environment variables
*/
EnvVarVaultNamespaceImport = "TERRAFORM_VAULT_NAMESPACE_IMPORT"
EnvVarSkipChildToken = "TERRAFORM_VAULT_SKIP_CHILD_TOKEN"
// EnvVarUsername to get the username for the userpass auth method
EnvVarUsername = "TERRAFORM_VAULT_USERNAME"
// EnvVarPassword to get the password for the userpass auth method
Expand Down
43 changes: 43 additions & 0 deletions internal/framework/base/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package base

import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/framework/validators"
)

// BaseModel describes common fields for all of the Terraform resource data models
type BaseModel struct {
Namespace types.String `tfsdk:"namespace"`
}

func baseSchema() map[string]schema.Attribute {
return map[string]schema.Attribute{
consts.FieldNamespace: schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
MarkdownDescription: "Target namespace. (requires Enterprise)",
Validators: []validator.String{
validators.PathValidator(),
},
},
}
}

func MustAddBaseSchema(s *schema.Schema) {
for k, v := range baseSchema() {
if _, ok := s.Attributes[k]; ok {
panic(fmt.Sprintf("cannot add schema field %q, already exists in the Schema map", k))
}

s.Attributes[k] = v
}
}
39 changes: 39 additions & 0 deletions internal/framework/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package client

import (
"context"
"fmt"
"os"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
"github.com/hashicorp/vault/api"
)

func GetClient(ctx context.Context, meta interface{}, namespace string) (*api.Client, error) {
var p *provider.ProviderMeta

switch v := meta.(type) {
case *provider.ProviderMeta:
p = v
default:
return nil, fmt.Errorf("meta argument must be a %T, not %T", p, meta)
}

ns := namespace
if namespace == "" {
// in order to import namespaced resources the user must provide
// the namespace from an environment variable.
ns = os.Getenv(consts.EnvVarVaultNamespaceImport)
if ns != "" {
tflog.Debug(ctx, fmt.Sprintf("Value for %q set from environment", consts.FieldNamespace))
}
}

if ns != "" {
return p.GetNSClient(ns)
}

return p.GetClient()
}
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).
62 changes: 62 additions & 0 deletions internal/framework/validators/file_exists.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validators

import (
"context"
"fmt"
"os"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/mitchellh/go-homedir"
)

var _ validator.String = fileExists{}

// fileExists validates that a given token is a valid initialization token
type fileExists struct{}

// Description describes the validation in plain text formatting.
func (v fileExists) Description(_ context.Context) string {
return "value must be a valid path to an existing file"
}

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

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

value := request.ConfigValue.ValueString()
if value == "" {
response.Diagnostics.AddError("invalid file", "value cannot be empty")
return
}

filename, err := homedir.Expand(value)
if err != nil {
response.Diagnostics.AddError("invalid file", err.Error())
return
}

st, err := os.Stat(filename)
if err != nil {
response.Diagnostics.AddError("invalid file", fmt.Sprintf("failed to stat path %q, err=%s", filename, err))
return
}

if st.IsDir() {
response.Diagnostics.AddError("invalid file", fmt.Sprintf("path %q is not a file", filename))
return
}
}

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

package validators

import (
"context"
"testing"

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

const testFilePath = "./testdata/fake_account.json"

func TestFrameworkProvider_FileExistsValidator(t *testing.T) {
cases := map[string]struct {
configValue func(t *testing.T) types.String
expectedErrorCount int
}{
"file-is-valid": {
configValue: func(t *testing.T) types.String {
return types.StringValue(testFilePath) // Path to a test fixture
},
},
"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,
},
"empty-string-is-not-valid": {
configValue: func(t *testing.T) types.String {
return types.StringValue("")
},
expectedErrorCount: 1,
},
"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{},
}

f := FileExistsValidator()

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

// Assert
if resp.Diagnostics.ErrorsCount() != tc.expectedErrorCount {
t.Errorf("Expected %d errors, got %d", tc.expectedErrorCount, resp.Diagnostics.ErrorsCount())
}
})
}
}
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{}
}
Loading

0 comments on commit 3ddd03e

Please sign in to comment.