Skip to content

[WIP] Plugin framework provider implementation #1603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ toolchain go1.24.1
require (
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/terraform-plugin-framework v1.14.1
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0
github.com/hashicorp/terraform-plugin-go v0.26.0
github.com/hashicorp/terraform-plugin-mux v0.18.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
github.com/stretchr/testify v1.10.0
github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f
Expand Down Expand Up @@ -45,7 +49,6 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.22.0 // indirect
github.com/hashicorp/terraform-json v0.24.0 // indirect
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,16 @@ github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8
github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ=
github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q=
github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow=
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg=
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0=
github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M=
github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-mux v0.18.0 h1:7491JFSpWyAe0v9YqBT+kel7mzHAbO5EpxxT0cUL/Ms=
github.com/hashicorp/terraform-plugin-mux v0.18.0/go.mod h1:Ho1g4Rr8qv0qTJlcRKfjjXTIO67LNbDtM6r+zHUNHJQ=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4=
github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA=
Expand Down
73 changes: 63 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,80 @@
package main

import (
"context"
"flag"
"log"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server"
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/vmware/terraform-provider-nsxt/nsxt"
)

// func main() {
// debugMode := flag.Bool("debug", false, "Enable debug mode for debuggers like Delve (dlv).")
// flag.Parse()

// opts := &plugin.ServeOpts{
// ProviderFunc: func() *schema.Provider {
// return nsxt.Provider()
// },
// }

// if *debugMode {
// opts.Debug = true
// opts.ProviderAddr = "registry.terraform.io/vmware/nsxt"
// }

// plugin.Serve(opts)
// }

func main() {
debugMode := flag.Bool("debug", false, "Enable debug mode for debuggers like Delve (dlv).")
ctx := context.Background()

var debug bool

flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()

opts := &plugin.ServeOpts{
ProviderFunc: func() *schema.Provider {
return nsxt.Provider()
upgradedSdkServer, err := tf5to6server.UpgradeServer(
ctx,
nsxt.Provider().GRPCProvider, // Old terraform-plugin-sdk provider
)

if err != nil {
log.Fatal(err)
}

providers := []func() tfprotov6.ProviderServer{
providerserver.NewProtocol6(nsxt.NewFrameworkProvider()),
func() tfprotov6.ProviderServer {
return upgradedSdkServer
},
}

if *debugMode {
opts.Debug = true
opts.ProviderAddr = "registry.terraform.io/vmware/nsxt"
muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...)

if err != nil {
log.Fatal(err)
}

var serveOpts []tf6server.ServeOpt

if debug {
serveOpts = append(serveOpts, tf6server.WithManagedDebug())
}

plugin.Serve(opts)
err = tf6server.Serve(
"registry.terraform.io/<namespace>/<provider_name>",
muxServer.ProviderServer,
serveOpts...,
)

if err != nil {
log.Fatal(err)
}
}
130 changes: 130 additions & 0 deletions nsxt/customtypes/nsx_license_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* Copyright © 2024 Broadcom, Inc. All Rights Reserved.
SPDX-License-Identifier: MPL-2.0 */

package customtypes

import (
"context"
"fmt"
"regexp"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var (
_ basetypes.StringTypable = (*NSXLicenseType)(nil)
_ xattr.TypeWithValidate = (*NSXLicenseType)(nil)
)

// NSXLicenseType is an attribute type that represents a valid NSX license string. No semantic equality
type NSXLicenseType struct {
basetypes.StringType
}

// String returns a human readable string of the type name.
func (t NSXLicenseType) String() string {
return "customtypes.NSXLicenseType"
}

// ValueType returns the Value type.
func (t NSXLicenseType) ValueType(ctx context.Context) attr.Value {
return NSXLicense{}
}

// Equal returns true if the given type is equivalent.
func (t NSXLicenseType) Equal(o attr.Type) bool {
other, ok := o.(NSXLicenseType)

if !ok {
return false
}

return t.StringType.Equal(other.StringType)
}

// Validate implements type validation. This type requires the value provided to be a String value that is a valid NSX license.
// This utilizes the Go `net/netip` library for parsing so leading zeroes will be rejected as invalid.
func (t NSXLicenseType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics {
var diags diag.Diagnostics

if in.Type() == nil {
return diags
}

if !in.Type().Is(tftypes.String) {
err := fmt.Errorf("expected String value, received %T with value: %v", in, in)
diags.AddAttributeError(
path,
"MSX License Type Validation Error",
"An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+
"Please report the following to the provider developer:\n\n"+err.Error(),
)
return diags
}

if !in.IsKnown() || in.IsNull() {
return diags
}

var valueString string

if err := in.As(&valueString); err != nil {
diags.AddAttributeError(
path,
"NSX License Type Validation Error",
"An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+
"Please report the following to the provider developer:\n\n"+err.Error(),
)

return diags
}

r := regexp.MustCompile("^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$")
if !r.MatchString(valueString) {
diags.AddAttributeError(
path,
"Invalid NSX License",
fmt.Sprintf("Value %s must be a valid nsx license key matching: ^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$", valueString),
)

return diags
}

return diags
}

// ValueFromString returns a StringValuable type given a StringValue.
func (t NSXLicenseType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
return NSXLicense{
StringValue: in,
}, nil
}

// ValueFromTerraform returns a Value given a tftypes.Value. This is meant to convert the tftypes.Value into a more convenient Go type
// for the provider to consume the data with.
func (t NSXLicenseType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.StringType.ValueFromTerraform(ctx, in)

if err != nil {
return nil, err
}

stringValue, ok := attrValue.(basetypes.StringValue)

if !ok {
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
}

stringValuable, diags := t.ValueFromString(ctx, stringValue)

if diags.HasError() {
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags)
}

return stringValuable, nil
}
63 changes: 63 additions & 0 deletions nsxt/customtypes/nsx_license_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Copyright © 2024 Broadcom, Inc. All Rights Reserved.
SPDX-License-Identifier: MPL-2.0 */

package customtypes

import (
"context"

Check failure on line 7 in nsxt/customtypes/nsx_license_value.go

View workflow job for this annotation

GitHub Actions / Build

File is not properly formatted (goimports)
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

var (
_ basetypes.StringValuable = (*NSXLicense)(nil)
)

// NSXLicense represents a valid NSX license string.
type NSXLicense struct {
basetypes.StringValue
}

// Type returns an NSXLicenseType.
func (v NSXLicense) Type(_ context.Context) attr.Type {
return NSXLicenseType{}
}

// Equal returns true if the given value is equivalent.
func (v NSXLicense) Equal(o attr.Value) bool {
other, ok := o.(NSXLicense)

if !ok {
return false
}

return v.StringValue.Equal(other.StringValue)
}

// NewNSXLicenseNull creates an NSXLicense with a null value. Determine whether the value is null via IsNull method.
func NewNSXLicenseNull() NSXLicense {
return NSXLicense{
StringValue: basetypes.NewStringNull(),
}
}

// NewNSXLicenseUnknown creates an NSXLicense with an unknown value. Determine whether the value is unknown via IsUnknown method.
func NewNSXLicenseUnknown() NSXLicense {
return NSXLicense{
StringValue: basetypes.NewStringUnknown(),
}
}

// NewNSXLicenseValue creates an NSXLicense with a known value. Access the value via ValueString method.
func NewNSXLicenseValue(value string) NSXLicense {
return NSXLicense{
StringValue: basetypes.NewStringValue(value),
}
}

// NewNSXLicensePointerValue creates an NSXLicense with a null value if nil or a known value. Access the value via ValueStringPointer method.
func NewNSXLicensePointerValue(value *string) NSXLicense {
return NSXLicense{
StringValue: basetypes.NewStringPointerValue(value),
}
}
65 changes: 65 additions & 0 deletions nsxt/customtypes/simple_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* Copyright © 2024 Broadcom, Inc. All Rights Reserved.
SPDX-License-Identifier: MPL-2.0 */

package customtypes

import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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"
)

func GetNsxIDSchema() schema.StringAttribute {
return schema.StringAttribute{
Optional: true,
Computed: true,
Description: "NSX ID for this resource",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
}
}

func GetPathSchema() schema.StringAttribute {
return schema.StringAttribute{
Computed: true,
Description: "Policy path for this resource",
}
}

func GetDisplayNameSchema() schema.StringAttribute {
return schema.StringAttribute{
Required: true,
Description: "Display name for this resource",
}
}

func GetDescriptionSchema() schema.StringAttribute {
return schema.StringAttribute{
Optional: true,
Description: "Description for this resource",
}
}

func GetRevisionSchema() schema.Int64Attribute {
return schema.Int64Attribute{
Computed: true,
Description: "The _revision property describes the current revision of the resource. To prevent clients from overwriting each other's changes, PUT operations must include the current _revision of the resource, which clients should obtain by issuing a GET operation. If the _revision provided in a PUT request is missing or stale, the operation will be rejected",
}
}

func GetContextSchema() schema.SingleNestedBlock {
return schema.SingleNestedBlock{

Attributes: map[string]schema.Attribute{
"project_id": schema.StringAttribute{
Description: "Id of the project which the resource belongs to.",
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
},
}
}
Loading