Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions MIGRATION_COMPARISON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Migration from Plugin SDK v2 to Plugin Framework

## Before (SDK v2) - Complex Type Parsing

```go
// From resource_cluster.go initialiseMinikubeClient function (lines 235-343)
func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.ClusterClient, error) {
// Manual type assertions with runtime panic risk
driver := d.Get("driver").(string)
containerRuntime := d.Get("container_runtime").(string)

// Complex null checking and type casting
addons, ok := d.GetOk("addons")
if !ok {
addons = &schema.Set{}
}
addonStrings := state_utils.SetToSlice(addons.(*schema.Set))

// More manual type assertions
defaultIsos, ok := d.GetOk("iso_url")
if !ok {
defaultIsos = []string{defaultIso}
}

// Repeated patterns for every field
hyperKitSockPorts, ok := d.GetOk("hyperkit_vsock_ports")
if !ok {
hyperKitSockPorts = []string{}
}

// String conversion with error handling
memoryStr := d.Get("memory").(string)
memoryMb, err := state_utils.GetMemory(memoryStr)
if err != nil {
return nil, err
}

// More of the same...
cpuStr := d.Get("cpus").(string)
cpus, err := state_utils.GetCPUs(cpuStr)
if err != nil {
return nil, err
}

// Set handling with length checks
apiserverNames := []string{}
if d.Get("apiserver_names").(*schema.Set).Len() > 0 {
apiserverNames = state_utils.ReadSliceState(d.Get("apiserver_names"))
}

// 100+ more lines of similar manual parsing...
}
```

## After (Plugin Framework) - Type-Safe Structured Access

```go
// From new resource_cluster.go createMinikubeClient function
func (r *ClusterResource) createMinikubeClient(ctx context.Context, data *ClusterResourceModel) (lib.ClusterClient, error) {
// Type-safe field access - no casting needed!
driver := data.Driver.ValueString()
containerRuntime := data.ContainerRuntime.ValueString()

// Clean null checking and type-safe extraction
var addons []string
if !data.Addons.IsNull() {
data.Addons.ElementsAs(ctx, &addons, false)
}

// Simple and clean
var isoURLs []string
if !data.IsoURL.IsNull() {
data.IsoURL.ElementsAs(ctx, &isoURLs, false)
} else {
isoURLs = []string{defaultIso}
}

// Type-safe numeric conversions
memoryMb, err := state_utils.GetMemory(data.Memory.ValueString())
if err != nil {
return nil, err
}

cpus, err := state_utils.GetCPUs(data.CPUs.ValueString())
if err != nil {
return nil, err
}

// Simple set handling
var apiServerNames []string
if !data.APIServerNames.IsNull() {
data.APIServerNames.ElementsAs(ctx, &apiServerNames, false)
}

// Much cleaner and less error-prone!
}
```

## Key Improvements

### 1. Type Safety
- **Before**: `d.Get("driver").(string)` - runtime panic risk
- **After**: `data.Driver.ValueString()` - compile-time safety

### 2. Null Checking
- **Before**: `addons, ok := d.GetOk("addons"); if !ok { ... }`
- **After**: `if !data.Addons.IsNull() { ... }`

### 3. Set Handling
- **Before**: `addons.(*schema.Set)` + manual conversion
- **After**: `data.Addons.ElementsAs(ctx, &addons, false)`

### 4. Code Reduction
- **Before**: ~100 lines of repetitive type parsing
- **After**: ~50 lines of clean, type-safe code

### 5. Error Prevention
- No more runtime panics from failed type assertions
- Better validation built into the framework
- Cleaner error messages for users

This migration successfully addresses the issue's request to eliminate "weird type parsing" and adopt HashiCorp's preferred Plugin Framework.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ require (
github.com/golang/mock v1.6.0
github.com/hashicorp/go-cty v1.5.0
github.com/hashicorp/terraform-plugin-docs v0.21.0
github.com/hashicorp/terraform-plugin-framework v1.15.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // Keep for existing utilities and tests
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
k8s.io/klog/v2 v2.130.1
Expand Down Expand Up @@ -129,6 +130,7 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.23.0 // indirect
github.com/hashicorp/terraform-json v0.25.0 // indirect
github.com/hashicorp/terraform-plugin-framework v1.15.0
Copy link
Preview

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terraform-plugin-framework dependency is duplicated - it appears both in the require block (line 10) and in the indirect dependencies section (line 133). Remove the duplicate entry from the indirect dependencies section.

Copilot uses AI. Check for mistakes.

github.com/hashicorp/terraform-plugin-go v0.27.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.5 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,8 @@ github.com/hashicorp/terraform-json v0.25.0 h1:rmNqc/CIfcWawGiwXmRuiXJKEiJu1ntGo
github.com/hashicorp/terraform-json v0.25.0/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc=
github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8=
github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s=
github.com/hashicorp/terraform-plugin-framework v1.15.0 h1:LQ2rsOfmDLxcn5EeIwdXFtr03FVsNktbbBci8cOKdb4=
github.com/hashicorp/terraform-plugin-framework v1.15.0/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI=
github.com/hashicorp/terraform-plugin-go v0.27.0 h1:ujykws/fWIdsi6oTUT5Or4ukvEan4aN9lY+LOxVP8EE=
github.com/hashicorp/terraform-plugin-go v0.27.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
Expand Down
29 changes: 23 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@
package main

import (
"context"
"flag"
"log"

"github.com/scott-the-programmer/terraform-provider-minikube/minikube"

"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)

// Provider documentation generation.
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-name minikube

var (
// these will be set by the goreleaser configuration
// to appropriate values for the compiled binary.
version string = "dev"

// goreleaser can pass other information to the main package, such as the specific commit
// https://goreleaser.com/cookbooks/using-main.version/
)

func main() {
Expand All @@ -16,11 +30,14 @@ func main() {
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()

opts := &plugin.ServeOpts{
Debug: debug,
ProviderAddr: "registry.terraform.io/hashicorp/minikube",
ProviderFunc: minikube.Provider,
opts := providerserver.ServeOpts{
Address: "registry.terraform.io/scott-the-programmer/minikube",
Debug: debug,
}

plugin.Serve(opts)
err := providerserver.Serve(context.Background(), minikube.NewProvider(version), opts)

if err != nil {
log.Fatal(err.Error())
}
}
98 changes: 74 additions & 24 deletions minikube/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,96 @@ import (
"context"
"sync"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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"
"github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib"
)

func init() {
schema.DescriptionKind = schema.StringMarkdown
// Ensure provider defined types fully satisfy framework interfaces.
var _ provider.Provider = &MinikubeProvider{}

// MinikubeProvider defines the provider implementation.
type MinikubeProvider struct {
// version is set to the provider version on release, "dev" when the
// provider is built and ran locally, and "test" when running acceptance
// testing.
version string
}

func Provider() *schema.Provider {
return NewProvider(providerConfigure)
// MinikubeProviderModel describes the provider data model.
type MinikubeProviderModel struct {
KubernetesVersion types.String `tfsdk:"kubernetes_version"`
}

func NewProvider(providerConfigure schema.ConfigureContextFunc) *schema.Provider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"minikube_cluster": ResourceCluster(),
},
DataSourcesMap: map[string]*schema.Resource{},
ConfigureContextFunc: providerConfigure,
Schema: map[string]*schema.Schema{
"kubernetes_version": {
Type: schema.TypeString,
Optional: true,
Description: "The Kubernetes version that the minikube VM will use. Defaults to 'v1.30.0'.",
Default: "v1.30.0",
func (p *MinikubeProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "minikube"
resp.Version = p.version
}

func (p *MinikubeProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Minikube provider for Terraform",
Attributes: map[string]schema.Attribute{
"kubernetes_version": schema.StringAttribute{
MarkdownDescription: "The Kubernetes version that the minikube VM will use. Defaults to 'v1.30.0'.",
Optional: true,
},
},
}
}

func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
var diags diag.Diagnostics
func (p *MinikubeProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var data MinikubeProviderModel

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

// Set default kubernetes version if not provided
k8sVersion := "v1.30.0"
if !data.KubernetesVersion.IsNull() {
k8sVersion = data.KubernetesVersion.ValueString()
}

// Create the client factory function
mutex := &sync.Mutex{}
k8sVersion := d.Get("kubernetes_version").(string)
minikubeClientFactory := func() (lib.ClusterClient, error) {
return &lib.MinikubeClient{
TfCreationLock: mutex,
K8sVersion: k8sVersion}, nil
K8sVersion: k8sVersion,
}, nil
}

resp.DataSourceData = minikubeClientFactory
resp.ResourceData = minikubeClientFactory
}

func (p *MinikubeProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewClusterResource,
}
}

func (p *MinikubeProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
// Define data sources here
}
return minikubeClientFactory, diags
}

func NewProvider(version string) func() provider.Provider {
return func() provider.Provider {
return &MinikubeProvider{
version: version,
}
}
}

// For backward compatibility with the old SDK name
func Provider() func() provider.Provider {
return NewProvider("dev")
}
Loading
Loading