Skip to content

Commit

Permalink
Merge pull request #873 from hashicorp/nf/apr23-mux-new-framework
Browse files Browse the repository at this point in the history
Integrate terraform-plugin-framework, and rewrite the tfe_variable resource
  • Loading branch information
nfagerlund authored May 9, 2023
2 parents 12a2df3 + aad4a6e commit e133a04
Show file tree
Hide file tree
Showing 15 changed files with 886 additions and 463 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ issues:
text: "S1019: should use make"
- linters:
- staticcheck
path: (resource_tfe_team_access_migrate_test|resource_tfe_variable_migrate_test|workspace_helpers_test)\.go
path: (resource_tfe_team_access_migrate_test|workspace_helpers_test)\.go
text: "SA1012: do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use"
- linters:
- staticcheck
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ ENHANCEMENTS:

BUG FIXES:

* `r/tfe_variable`: Don't silently erase or override the `value` of a sensitive variable on changes to other attributes when `ignore_changes = [value]` is set, by @nfagerlund ([#873](https://github.com/hashicorp/terraform-provider-tfe/pull/873), fixing issue [#839](https://github.com/hashicorp/terraform-provider-tfe/issues/839))

## v0.44.1 (April 21, 2023)

BUG FIXES:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ require (
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
github.com/hashicorp/hcl/v2 v2.16.2 // indirect
github.com/hashicorp/terraform-plugin-framework v1.2.0
github.com/hashicorp/terraform-plugin-framework-validators v0.10.0
github.com/hashicorp/terraform-plugin-go v0.15.0
github.com/hashicorp/terraform-plugin-mux v0.10.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX
github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980=
github.com/hashicorp/terraform-json v0.16.0 h1:UKkeWRWb23do5LNAFlh/K3N0ymn1qTOO8c+85Albo3s=
github.com/hashicorp/terraform-json v0.16.0/go.mod h1:v0Ufk9jJnk6tcIZvScHvetlKfiNTC+WS21mnXIlc0B0=
github.com/hashicorp/terraform-plugin-framework v1.2.0 h1:MZjFFfULnFq8fh04FqrKPcJ/nGpHOvX4buIygT3MSNY=
github.com/hashicorp/terraform-plugin-framework v1.2.0/go.mod h1:nToI62JylqXDq84weLJ/U3umUsBhZAaTmU0HXIVUOcw=
github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 h1:4L0tmy/8esP6OcvocVymw52lY0HyQ5OxB7VNl7k4bS0=
github.com/hashicorp/terraform-plugin-framework-validators v0.10.0/go.mod h1:qdQJCdimB9JeX2YwOpItEu+IrfoJjWQ5PhLpAOMDQAE=
github.com/hashicorp/terraform-plugin-go v0.15.0 h1:1BJNSUFs09DS8h/XNyJNJaeusQuWc/T9V99ylU9Zwp0=
github.com/hashicorp/terraform-plugin-go v0.15.0/go.mod h1:tk9E3/Zx4RlF/9FdGAhwxHExqIHHldqiQGt20G6g+nQ=
github.com/hashicorp/terraform-plugin-log v0.8.0 h1:pX2VQ/TGKu+UU1rCay0OlzosNKe4Nz1pepLXj95oyy0=
Expand Down
22 changes: 14 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"log"
"os"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server"
"github.com/hashicorp/terraform-plugin-mux/tf5muxserver"
"github.com/hashicorp/terraform-provider-tfe/tfe"
Expand Down Expand Up @@ -36,17 +37,22 @@ func main() {
// terraform-plugin-mux here is used to combine multiple Terraform providers
// built using different SDK and frameworks in order to combine them into a
// single logical provider for Terraform to work with.
// Here, we use one provider (tfe.Provider) that relies on the standard
// terraform-plugin-sdk, and this is the main framework for used in this
// provider. The second provider (tfe.PluginProviderServer) relies on the
// lower level terraform-plugin-go to handle far more complex behavior, and
// only should be used for functionality that is not present in the
// common terraform-plugin- sdk framework.
// - The classic provider relies on terraform-plugin-sdk, and has the bulk
// of the resources and data sources.
// - The "next" provider relies on the newer terraform-plugin-framework, and
// we expect to migrate resources and data sources to it over time.
// - The low-level provider relies on terraform-plugin-go to handle more
// complex behavior, and should only be used for functionality that is not
// available otherwise. We suspect the framework can supplant it, but have
// not proven that out yet.
nextProvider := providerserver.NewProtocol5(tfe.NewFrameworkProvider())
classicProvider := tfe.Provider().GRPCProvider
lowLevelProvider := tfe.PluginProviderServer
mux, err := tf5muxserver.NewMuxServer(
ctx, tfe.Provider().GRPCProvider, tfe.PluginProviderServer,
ctx, nextProvider, classicProvider, lowLevelProvider,
)
if err != nil {
log.Printf("[ERROR] Could not setup a NewSchemaServerFactory using the providers: %v", err)
log.Printf("[ERROR] Could not setup a mux server using the internal providers: %v", err)
os.Exit(1)
}

Expand Down
8 changes: 4 additions & 4 deletions tfe/data_source_variable_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ func TestAccTFEVariableSetsDataSource_basic(t *testing.T) {
orgName := fmt.Sprintf("org-%d", rInt)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEVariableSetsDataSourceConfig_basic(rInt),
Expand All @@ -43,8 +43,8 @@ func TestAccTFEVariableSetsDataSource_full(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEVariableSetsDataSourceConfig_full(rInt),
Expand Down
4 changes: 2 additions & 2 deletions tfe/data_source_variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ func TestAccTFEVariablesDataSource_basic(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEVariablesDataSourceConfig_basic(rInt),
Expand Down
8 changes: 7 additions & 1 deletion tfe/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type ConfigHost struct {
Services map[string]interface{} `hcl:"services"`
}

// ConfiguredClient wraps the tfe.Client the provider uses, plus the default
// organization name to be used by resources that need an organization but don't
// specify one.
type ConfiguredClient struct {
Client *tfe.Client
Organization string
Expand Down Expand Up @@ -162,7 +165,6 @@ func Provider() *schema.Provider {
"tfe_terraform_version": resourceTFETerraformVersion(),
"tfe_workspace": resourceTFEWorkspace(),
"tfe_workspace_run_task": resourceTFEWorkspaceRunTask(),
"tfe_variable": resourceTFEVariable(),
"tfe_variable_set": resourceTFEVariableSet(),
"tfe_workspace_variable_set": resourceTFEWorkspaceVariableSet(),
"tfe_workspace_policy_set": resourceTFEWorkspacePolicySet(),
Expand Down Expand Up @@ -215,6 +217,10 @@ func getTokenFromCreds(services *disco.Disco, hostname svchost.Hostname) string
return ""
}

// getClient encapsulates the logic for configuring a go-tfe client instance for
// the provider, including fallback to values from environment variables. This
// is useful because we're muxing multiple provider servers together and each
// one needs an identically configured client.
func getClient(tfeHost, token string, insecure bool) (*tfe.Client, error) {
h := tfeHost
if tfeHost == "" {
Expand Down
121 changes: 121 additions & 0 deletions tfe/provider_next.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package tfe

import (
"context"
"os"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// frameworkProvider is a type that implements the terraform-plugin-framework
// provider.Provider interface. Someday, this will probably encompass the entire
// behavior of the tfe provider. Today, it is a small but growing subset.
type frameworkProvider struct{}

// Compile-time interface check
var _ provider.Provider = &frameworkProvider{}

// FrameworkProviderConfig is a helper type for extracting the provider
// configuration from the provider block.
type FrameworkProviderConfig struct {
Hostname types.String `tfsdk:"hostname"`
Token types.String `tfsdk:"token"`
Organization types.String `tfsdk:"organization"`
SSLSkipVerify types.Bool `tfsdk:"ssl_skip_verify"`
}

// NewFrameworkProvider is a helper function for initializing the portion of
// the tfe provider implemented via the terraform-plugin-framework.
func NewFrameworkProvider() provider.Provider {
return &frameworkProvider{}
}

// Metadata (a Provider interface function) lets the provider identify itself.
// Resources and data sources can access this information from their request
// objects.
func (p *frameworkProvider) Metadata(_ context.Context, _ provider.MetadataRequest, res *provider.MetadataResponse) {
res.TypeName = "tfe"
}

// Schema (a Provider interface function) returns the schema for the Terraform
// block that configures the provider itself.
func (p *frameworkProvider) Schema(_ context.Context, _ provider.SchemaRequest, res *provider.SchemaResponse) {
res.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"hostname": schema.StringAttribute{
Description: descriptions["hostname"],
Optional: true,
},
"token": schema.StringAttribute{
Optional: true,
Description: descriptions["token"],
// TODO: should be sensitive, but that's a breaking change.
},
"organization": schema.StringAttribute{
Description: descriptions["organization"],
Optional: true,
},
"ssl_skip_verify": schema.BoolAttribute{
Description: descriptions["ssl_skip_verify"],
Optional: true,
},
},
}
}

// Configure (a Provider interface function) sets up the TFC client per the
// specified provider configuration block and env vars.
func (p *frameworkProvider) Configure(ctx context.Context, req provider.ConfigureRequest, res *provider.ConfigureResponse) {
var data FrameworkProviderConfig
diags := req.Config.Get(ctx, &data)

res.Diagnostics.Append(diags...)
if res.Diagnostics.HasError() {
return
}

// TODO: add .IsUnknown() error handling in the case where user passed
// unresolvable references for provider config values, c.f. what hashicups
// does around
// https://github.com/hashicorp/terraform-provider-hashicups-pf/blob/main/hashicups/provider.go#L79

// Read default organization from environment if it wasn't set in the
// config. All other env defaults are handled by getClient().
if data.Organization.IsNull() {
// Falling back to Getenv will collapse the new type system's handling
// of null/unknown into a plain zero-value, but that's OK at this point.
data.Organization = types.StringValue(os.Getenv("TFE_ORGANIZATION"))
}

client, err := getClient(data.Hostname.ValueString(), data.Token.ValueString(), data.SSLSkipVerify.ValueBool())

if err != nil {
res.Diagnostics.AddError("Failed to initialize HTTP client", err.Error())
return
}

configuredClient := ConfiguredClient{
Client: client,
Organization: data.Organization.ValueString(),
}

res.DataSourceData = configuredClient
res.ResourceData = configuredClient
}

func (p *frameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{}
}

func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewResourceVariable,
}
}
5 changes: 4 additions & 1 deletion tfe/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-mux/tf5muxserver"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand All @@ -35,8 +36,10 @@ func init() {
testAccMuxedProviders = map[string]func() (tfprotov5.ProviderServer, error){
"tfe": func() (tfprotov5.ProviderServer, error) {
ctx := context.Background()
nextProvider := providerserver.NewProtocol5(NewFrameworkProvider())

mux, err := tf5muxserver.NewMuxServer(
ctx, PluginProviderServer, testAccProvider.GRPCProvider,
ctx, nextProvider, PluginProviderServer, testAccProvider.GRPCProvider,
)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit e133a04

Please sign in to comment.