From e30cb2d26aa8fcc8ed406331c5d4d6f844e57d50 Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 21:48:25 +1300 Subject: [PATCH 01/16] implement no-limit and max for memory --- minikube/generator/schema_builder.go | 6 ++-- minikube/lib/memory.go | 32 +++++++++++++++++++ minikube/resource_cluster.go | 2 +- minikube/state_utils/memory.go | 38 ++++++++++++++++++++++ minikube/state_utils/resource_size.go | 45 ++++++++++++++++++--------- 5 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 minikube/lib/memory.go create mode 100644 minikube/state_utils/memory.go diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index 366d799..d0accd1 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -58,7 +58,7 @@ var updateFields = []string{ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ "memory": { Default: "4g", - Description: "Amount of RAM to allocate to Kubernetes (format: [(case-insensitive)], where unit = b, k, kb, m, mb, g or gb)", + Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \"max\" to use the maximum amount of memory. Use \"no-limit\" to not specify a limit (Docker/Podman only))", Type: String, StateFunc: "state_utils.ResourceSizeConverter()", ValidateDiagFunc: "state_utils.ResourceSizeValidator()", @@ -72,8 +72,8 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ }, "cpus": { Default: "2", - Description: "Amount of CPUs to allocate to Kubernetes", - Type: Int, + Description: "Number of CPUs allocated to Kubernetes. Use \"max\" to use the maximum number of CPUs. Use \"no-limit\" to not specify a limit (Docker/Podman only)", + Type: String, }, // Customize the description to be the fullset of drivers "driver": { diff --git a/minikube/lib/memory.go b/minikube/lib/memory.go new file mode 100644 index 0000000..d8bc17d --- /dev/null +++ b/minikube/lib/memory.go @@ -0,0 +1,32 @@ +package lib + +import ( + "k8s.io/minikube/pkg/minikube/machine" +) + + +var NoLimit = "no-limit" +var Max = "max" + +// MemoryInfo holds system and container memory information +type MemoryInfo struct { + SystemMemory int +} + +// GetMemoryLimits returns the amount of memory allocated to the system and container +// The return values are in MiB +func GetMemoryLimit() (*MemoryInfo, error) { + info, _, memErr, _ := machine.LocalHostInfo() + + if memErr != nil { + return nil, memErr + } + + // Subtract 1gb for overhead + memInfo := &MemoryInfo{ + SystemMemory: int(info.Memory) - 1024, + } + + return memInfo, nil +} + diff --git a/minikube/resource_cluster.go b/minikube/resource_cluster.go index 13ad323..1d44e38 100644 --- a/minikube/resource_cluster.go +++ b/minikube/resource_cluster.go @@ -271,7 +271,7 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste } memoryStr := d.Get("memory").(string) - memoryMb, err := pkgutil.CalculateSizeInMB(memoryStr) + memoryMb, err := state_utils.GetMemory(memoryStr); if err != nil { return nil, err } diff --git a/minikube/state_utils/memory.go b/minikube/state_utils/memory.go new file mode 100644 index 0000000..71e417c --- /dev/null +++ b/minikube/state_utils/memory.go @@ -0,0 +1,38 @@ +package state_utils + +import ( + "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" + pkgutil "k8s.io/minikube/pkg/util" +) + +func GetMemory(memoryStr string) (int, error) { + var memoryMb int + var err error + if memoryStr == lib.Max { + memoryInfo, err := lib.GetMemoryLimit() + if err != nil { + return 0, err + } + + memoryMb = memoryInfo.SystemMemory + } else if memoryStr == lib.NoLimit { + memoryMb = 0 + } else { + err = ResourceSizeValidatorImpl(memoryStr) + if err != nil { + return 0, err + } + + memoryStr, err = ResourceSizeConverterImpl(memoryStr) + if err != nil { + return 0, err + } + + memoryMb, err = pkgutil.CalculateSizeInMB(memoryStr) + if err != nil { + return 0, err + } + } + + return memoryMb, err +} diff --git a/minikube/state_utils/resource_size.go b/minikube/state_utils/resource_size.go index e211c06..a31e29d 100644 --- a/minikube/state_utils/resource_size.go +++ b/minikube/state_utils/resource_size.go @@ -13,31 +13,46 @@ import ( func ResourceSizeConverter() schema.SchemaStateFunc { return func(val interface{}) string { - size, ok := val.(string) - if !ok { - panic(errors.New("resource size is not a string")) - } - sizeMb, err := pkgutil.CalculateSizeInMB(size) + result, err := ResourceSizeConverterImpl(val) if err != nil { - panic(errors.New("invalid resource size value")) + panic(err) } + return result + } +} - return strconv.Itoa(sizeMb) + "mb" +func ResourceSizeConverterImpl(val interface{}) (string, error) { + size, ok := val.(string) + if !ok { + return "", errors.New("resource size is not a string") } + sizeMb, err := pkgutil.CalculateSizeInMB(size) + if err != nil { + return "", errors.New("invalid resource size value") + } + + return strconv.Itoa(sizeMb) + "mb", nil } func ResourceSizeValidator() schema.SchemaValidateDiagFunc { return schema.SchemaValidateDiagFunc(func(val interface{}, path cty.Path) diag.Diagnostics { - size, ok := val.(string) - if !ok { - diag := diag.FromErr(errors.New("resource size is not a string")) - return diag - } - _, err := pkgutil.CalculateSizeInMB(size) + err := ResourceSizeValidatorImpl(val) if err != nil { - diag := diag.FromErr(errors.New("invalid resource size value")) - return diag + return diag.FromErr(err) } return nil + }) } + +func ResourceSizeValidatorImpl(val interface{}) error { + size, ok := val.(string) + if !ok { + return errors.New("resource size is not a string") + } + _, err := pkgutil.CalculateSizeInMB(size) + if err != nil { + return errors.New("invalid resource size value") + } + return nil +} From ce7d2c632fbdecdfe8333857ba86f8554785f1db Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 21:57:40 +1300 Subject: [PATCH 02/16] remove unused transform/validate functions --- minikube/generator/schema_builder.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index d0accd1..3aca1d0 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -60,8 +60,6 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ Default: "4g", Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \"max\" to use the maximum amount of memory. Use \"no-limit\" to not specify a limit (Docker/Podman only))", Type: String, - StateFunc: "state_utils.ResourceSizeConverter()", - ValidateDiagFunc: "state_utils.ResourceSizeValidator()", }, "disk_size": { Default: "20000mb", From 7980799f81326e4e01e61832cce7efd9a4e0f8ae Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 21:57:51 +1300 Subject: [PATCH 03/16] update test output --- minikube/generator/schema_builder_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/minikube/generator/schema_builder_test.go b/minikube/generator/schema_builder_test.go index dda5ebe..6c450c8 100644 --- a/minikube/generator/schema_builder_test.go +++ b/minikube/generator/schema_builder_test.go @@ -360,14 +360,12 @@ func TestOverride(t *testing.T) { assert.Equal(t, header+` "memory": { Type: schema.TypeString, - Description: "Amount of RAM to allocate to Kubernetes (format: [(case-insensitive)], where unit = b, k, kb, m, mb, g or gb)", + Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use "max" to use the maximum amount of memory. Use "no-limit" to not specify a limit (Docker/Podman only))", Optional: true, ForceNew: true, Default: "4g", - StateFunc: state_utils.ResourceSizeConverter(), - ValidateDiagFunc: state_utils.ResourceSizeValidator(), }, } From 664a9dadb8a18d4c62b0609b0ff62f322fcae6e0 Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 22:01:50 +1300 Subject: [PATCH 04/16] add tests for memory util --- minikube/state_utils/memory_test.go | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 minikube/state_utils/memory_test.go diff --git a/minikube/state_utils/memory_test.go b/minikube/state_utils/memory_test.go new file mode 100644 index 0000000..16791a2 --- /dev/null +++ b/minikube/state_utils/memory_test.go @@ -0,0 +1,67 @@ +package state_utils + +import ( + "testing" + + "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" + "github.com/stretchr/testify/assert" +) + +func TestGetMemory(t *testing.T) { + tests := []struct { + name string + input string + expected int + expectError bool + }{ + { + name: "valid memory size - 2G", + input: "2G", + expected: 2048, + expectError: false, + }, + { + name: "valid memory size - 1024mb", + input: "1024mb", + expected: 1024, + expectError: false, + }, + { + name: "no limit case", + input: lib.NoLimit, + expected: 0, + expectError: false, + }, + { + name: "invalid memory size", + input: "invalid", + expected: 0, + expectError: true, + }, + { + name: "negative memory size", + input: "-1G", + expected: 0, + expectError: true, + }, + { + name: "empty string", + input: "", + expected: 0, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := GetMemory(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} From dd3eb7ebbde67c6473ef88806268892af02f1b28 Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 22:06:02 +1300 Subject: [PATCH 05/16] regenerate schema --- minikube/schema_cluster.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/minikube/schema_cluster.go b/minikube/schema_cluster.go index aefda1c..a42d138 100644 --- a/minikube/schema_cluster.go +++ b/minikube/schema_cluster.go @@ -191,13 +191,13 @@ var ( }, "cpus": { - Type: schema.TypeInt, - Description: "Amount of CPUs to allocate to Kubernetes", + Type: schema.TypeString, + Description: "Number of CPUs allocated to Kubernetes. Use "max" to use the maximum number of CPUs. Use "no-limit" to not specify a limit (Docker/Podman only)", Optional: true, ForceNew: true, - Default: 2, + Default: "2", }, "cri_socket": { @@ -668,14 +668,12 @@ var ( "memory": { Type: schema.TypeString, - Description: "Amount of RAM to allocate to Kubernetes (format: [(case-insensitive)], where unit = b, k, kb, m, mb, g or gb)", + Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use "max" to use the maximum amount of memory. Use "no-limit" to not specify a limit (Docker/Podman only))", Optional: true, ForceNew: true, Default: "4g", - StateFunc: state_utils.ResourceSizeConverter(), - ValidateDiagFunc: state_utils.ResourceSizeValidator(), }, "mount": { From 47f540604548bc436872be4d546689fd773ab793 Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 22:14:21 +1300 Subject: [PATCH 06/16] ensure quotes are escaped during build --- minikube/generator/schema_builder.go | 4 ++-- minikube/generator/schema_builder_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index 3aca1d0..7dd6d04 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -58,7 +58,7 @@ var updateFields = []string{ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ "memory": { Default: "4g", - Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \"max\" to use the maximum amount of memory. Use \"no-limit\" to not specify a limit (Docker/Podman only))", + Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \\\"max\\\" to use the maximum amount of memory. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only))", Type: String, }, "disk_size": { @@ -70,7 +70,7 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ }, "cpus": { Default: "2", - Description: "Number of CPUs allocated to Kubernetes. Use \"max\" to use the maximum number of CPUs. Use \"no-limit\" to not specify a limit (Docker/Podman only)", + Description: "Number of CPUs allocated to Kubernetes. Use \\\"max\\\" to use the maximum number of CPUs. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only)", Type: String, }, // Customize the description to be the fullset of drivers diff --git a/minikube/generator/schema_builder_test.go b/minikube/generator/schema_builder_test.go index 6c450c8..7838157 100644 --- a/minikube/generator/schema_builder_test.go +++ b/minikube/generator/schema_builder_test.go @@ -360,7 +360,7 @@ func TestOverride(t *testing.T) { assert.Equal(t, header+` "memory": { Type: schema.TypeString, - Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use "max" to use the maximum amount of memory. Use "no-limit" to not specify a limit (Docker/Podman only))", + Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \"max\" to use the maximum amount of memory. Use \"no-limit\" to not specify a limit (Docker/Podman only))", Optional: true, ForceNew: true, From 0f3383caef575fbceb17e059c5799eeaf6cd03b8 Mon Sep 17 00:00:00 2001 From: scott-the-programmer Date: Thu, 20 Feb 2025 22:17:13 +1300 Subject: [PATCH 07/16] revert cpu type temporarily --- minikube/generator/schema_builder.go | 2 +- minikube/schema_cluster.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index 7dd6d04..9429c8a 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -71,7 +71,7 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ "cpus": { Default: "2", Description: "Number of CPUs allocated to Kubernetes. Use \\\"max\\\" to use the maximum number of CPUs. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only)", - Type: String, + Type: Int, }, // Customize the description to be the fullset of drivers "driver": { diff --git a/minikube/schema_cluster.go b/minikube/schema_cluster.go index a42d138..23e5f58 100644 --- a/minikube/schema_cluster.go +++ b/minikube/schema_cluster.go @@ -191,13 +191,13 @@ var ( }, "cpus": { - Type: schema.TypeString, - Description: "Number of CPUs allocated to Kubernetes. Use "max" to use the maximum number of CPUs. Use "no-limit" to not specify a limit (Docker/Podman only)", + Type: schema.TypeInt, + Description: "Number of CPUs allocated to Kubernetes. Use \"max\" to use the maximum number of CPUs. Use \"no-limit\" to not specify a limit (Docker/Podman only)", Optional: true, ForceNew: true, - Default: "2", + Default: 2, }, "cri_socket": { @@ -668,7 +668,7 @@ var ( "memory": { Type: schema.TypeString, - Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use "max" to use the maximum amount of memory. Use "no-limit" to not specify a limit (Docker/Podman only))", + Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \"max\" to use the maximum amount of memory. Use \"no-limit\" to not specify a limit (Docker/Podman only))", Optional: true, ForceNew: true, From ccf8e9ffbb8bf1ba90458162afd7b1b1a9e1c8d5 Mon Sep 17 00:00:00 2001 From: "Scott Murray (aider)" Date: Sat, 1 Mar 2025 12:50:29 +1300 Subject: [PATCH 08/16] feat: Add CPU max and no-limit support with new state utility function --- minikube/generator/schema_builder.go | 2 +- minikube/resource_cluster.go | 2 +- minikube/state_utils/cpu.go | 15 ++++++ minikube/state_utils/cpu_test.go | 68 ++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 minikube/state_utils/cpu.go create mode 100644 minikube/state_utils/cpu_test.go diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index 9429c8a..7dd6d04 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -71,7 +71,7 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ "cpus": { Default: "2", Description: "Number of CPUs allocated to Kubernetes. Use \\\"max\\\" to use the maximum number of CPUs. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only)", - Type: Int, + Type: String, }, // Customize the description to be the fullset of drivers "driver": { diff --git a/minikube/resource_cluster.go b/minikube/resource_cluster.go index 1d44e38..4c701f3 100644 --- a/minikube/resource_cluster.go +++ b/minikube/resource_cluster.go @@ -380,7 +380,7 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste KicBaseImage: d.Get("base_image").(string), Network: d.Get("network").(string), Memory: memoryMb, - CPUs: d.Get("cpus").(int), + CPUs: state_utils.GetCPUs(d.Get("cpus").(string)), DiskSize: diskMb, Driver: driver, ListenAddress: d.Get("listen_address").(string), diff --git a/minikube/state_utils/cpu.go b/minikube/state_utils/cpu.go new file mode 100644 index 0000000..501652f --- /dev/null +++ b/minikube/state_utils/cpu.go @@ -0,0 +1,15 @@ +package state_utils + +import ( + "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" + "runtime" +) + +func GetCPUs(cpuStr string) (int, error) { + if cpuStr == lib.Max { + return runtime.NumCPU(), nil + } else if cpuStr == lib.NoLimit { + return 0, nil + } + return cpuStr, nil +} diff --git a/minikube/state_utils/cpu_test.go b/minikube/state_utils/cpu_test.go new file mode 100644 index 0000000..767d0d3 --- /dev/null +++ b/minikube/state_utils/cpu_test.go @@ -0,0 +1,68 @@ +package state_utils + +import ( + "runtime" + "testing" + + "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" + "github.com/stretchr/testify/assert" +) + +func TestGetCPUs(t *testing.T) { + tests := []struct { + name string + input string + expected int + expectError bool + }{ + { + name: "valid CPU count", + input: "2", + expected: 2, + expectError: false, + }, + { + name: "max CPUs", + input: lib.Max, + expected: runtime.NumCPU(), + expectError: false, + }, + { + name: "no limit case", + input: lib.NoLimit, + expected: 0, + expectError: false, + }, + { + name: "invalid CPU count", + input: "invalid", + expected: 0, + expectError: true, + }, + { + name: "negative CPU count", + input: "-1", + expected: 0, + expectError: true, + }, + { + name: "empty string", + input: "", + expected: 0, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := GetCPUs(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} From 1825964cf015b495af43e85be6a8a0314d58458b Mon Sep 17 00:00:00 2001 From: "Scott Murray (aider)" Date: Sat, 1 Mar 2025 12:53:01 +1300 Subject: [PATCH 09/16] fix: Parse CPU string to integer in GetCPUs function --- minikube/state_utils/cpu.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/minikube/state_utils/cpu.go b/minikube/state_utils/cpu.go index 501652f..fe5f0d5 100644 --- a/minikube/state_utils/cpu.go +++ b/minikube/state_utils/cpu.go @@ -3,6 +3,7 @@ package state_utils import ( "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" "runtime" + "strconv" ) func GetCPUs(cpuStr string) (int, error) { @@ -11,5 +12,5 @@ func GetCPUs(cpuStr string) (int, error) { } else if cpuStr == lib.NoLimit { return 0, nil } - return cpuStr, nil + return strconv.Atoi(cpuStr) } From 0781c81328341d81acb2b9f699b45da410123be0 Mon Sep 17 00:00:00 2001 From: "Scott Murray (aider)" Date: Sat, 1 Mar 2025 12:53:56 +1300 Subject: [PATCH 10/16] fix: Handle negative CPU count and error cases in GetCPUs function --- minikube/resource_cluster.go | 8 +++++++- minikube/state_utils/cpu.go | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/minikube/resource_cluster.go b/minikube/resource_cluster.go index 4c701f3..97739e6 100644 --- a/minikube/resource_cluster.go +++ b/minikube/resource_cluster.go @@ -380,7 +380,13 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste KicBaseImage: d.Get("base_image").(string), Network: d.Get("network").(string), Memory: memoryMb, - CPUs: state_utils.GetCPUs(d.Get("cpus").(string)), + CPUs: func() int { + cpus, err := state_utils.GetCPUs(d.Get("cpus").(string)) + if err != nil { + return 2 // Default to 2 CPUs if there's an error + } + return cpus + }(), DiskSize: diskMb, Driver: driver, ListenAddress: d.Get("listen_address").(string), diff --git a/minikube/state_utils/cpu.go b/minikube/state_utils/cpu.go index fe5f0d5..35d0d89 100644 --- a/minikube/state_utils/cpu.go +++ b/minikube/state_utils/cpu.go @@ -12,5 +12,12 @@ func GetCPUs(cpuStr string) (int, error) { } else if cpuStr == lib.NoLimit { return 0, nil } - return strconv.Atoi(cpuStr) + cpus, err := strconv.Atoi(cpuStr) + if err != nil { + return 0, err + } + if cpus < 0 { + return 0, fmt.Errorf("CPU count cannot be negative: %d", cpus) + } + return cpus, nil } From f7483cdf71dd8ad6a32b6a0c8bc5c0fbe326c392 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 10 Mar 2025 00:25:38 +1300 Subject: [PATCH 11/16] chore: Import fmt package for potential error formatting in CPU utility --- minikube/state_utils/cpu.go | 1 + 1 file changed, 1 insertion(+) diff --git a/minikube/state_utils/cpu.go b/minikube/state_utils/cpu.go index 35d0d89..f8d7cd9 100644 --- a/minikube/state_utils/cpu.go +++ b/minikube/state_utils/cpu.go @@ -4,6 +4,7 @@ import ( "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" "runtime" "strconv" + "fmt" ) func GetCPUs(cpuStr string) (int, error) { From adb77cbe1d3fa219c732c13234159caa196e4bd3 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 10 Mar 2025 00:25:57 +1300 Subject: [PATCH 12/16] feat: Add memory converter and validator utility functions with comprehensive tests --- minikube/state_utils/memory.go | 51 +++++++++ minikube/state_utils/memory_test.go | 157 ++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/minikube/state_utils/memory.go b/minikube/state_utils/memory.go index 71e417c..2e365bf 100644 --- a/minikube/state_utils/memory.go +++ b/minikube/state_utils/memory.go @@ -1,6 +1,11 @@ package state_utils import ( + "errors" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" pkgutil "k8s.io/minikube/pkg/util" ) @@ -36,3 +41,49 @@ func GetMemory(memoryStr string) (int, error) { return memoryMb, err } + +func MemoryConverter() schema.SchemaStateFunc { + return func(val interface{}) string { + result, err := MemoryConverterImpl(val) + if err != nil { + panic(err) + } + return result + } +} + +func MemoryConverterImpl(val interface{}) (string, error) { + memoryStr, ok := val.(string) + if !ok { + return "", errors.New("memory value is not a string") + } + + if memoryStr == lib.Max || memoryStr == lib.NoLimit { + return memoryStr, nil + } + + return ResourceSizeConverterImpl(memoryStr) +} + +func MemoryValidator() schema.SchemaValidateDiagFunc { + return schema.SchemaValidateDiagFunc(func(val interface{}, path cty.Path) diag.Diagnostics { + err := MemoryValidatorImpl(val) + if err != nil { + return diag.FromErr(err) + } + return nil + }) +} + +func MemoryValidatorImpl(val interface{}) error { + memoryStr, ok := val.(string) + if !ok { + return errors.New("memory value is not a string") + } + + if memoryStr == lib.Max || memoryStr == lib.NoLimit { + return nil + } + + return ResourceSizeValidatorImpl(memoryStr) +} diff --git a/minikube/state_utils/memory_test.go b/minikube/state_utils/memory_test.go index 16791a2..519a8dd 100644 --- a/minikube/state_utils/memory_test.go +++ b/minikube/state_utils/memory_test.go @@ -65,3 +65,160 @@ func TestGetMemory(t *testing.T) { }) } } + +func TestMemoryConverterImpl(t *testing.T) { + tests := []struct { + name string + input interface{} + expected string + expectError bool + }{ + { + name: "valid memory size - 2G", + input: "2G", + expected: "2048mb", + expectError: false, + }, + { + name: "valid memory size - 1024mb", + input: "1024mb", + expected: "1024mb", + expectError: false, + }, + { + name: "max case", + input: lib.Max, + expected: lib.Max, + expectError: false, + }, + { + name: "no limit case", + input: lib.NoLimit, + expected: lib.NoLimit, + expectError: false, + }, + { + name: "non-string input", + input: 123, + expected: "", + expectError: true, + }, + { + name: "invalid memory size", + input: "invalid", + expected: "", + expectError: true, + }, + { + name: "empty string", + input: "", + expected: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := MemoryConverterImpl(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} + +func TestMemoryValidatorImpl(t *testing.T) { + tests := []struct { + name string + input interface{} + expectError bool + }{ + { + name: "valid memory size - 2G", + input: "2G", + expectError: false, + }, + { + name: "valid memory size - 1024mb", + input: "1024mb", + expectError: false, + }, + { + name: "max case", + input: lib.Max, + expectError: false, + }, + { + name: "no limit case", + input: lib.NoLimit, + expectError: false, + }, + { + name: "non-string input", + input: 123, + expectError: true, + }, + { + name: "invalid memory size", + input: "invalid", + expectError: true, + }, + { + name: "negative memory size", + input: "-1G", + expectError: true, + }, + { + name: "empty string", + input: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := MemoryValidatorImpl(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestMemoryConverter(t *testing.T) { + converter := MemoryConverter() + + // Test normal case + result := converter("2G") + assert.Equal(t, "2048mb", result) + + // Test special cases + assert.Equal(t, lib.Max, converter(lib.Max)) + assert.Equal(t, lib.NoLimit, converter(lib.NoLimit)) + + // Test panic case + assert.Panics(t, func() { + converter(123) // non-string input should panic + }) +} + +func TestMemoryValidator(t *testing.T) { + validator := MemoryValidator() + + // Test valid cases + assert.Nil(t, validator("2G", nil)) + assert.Nil(t, validator(lib.Max, nil)) + assert.Nil(t, validator(lib.NoLimit, nil)) + + // Test invalid cases + assert.NotNil(t, validator(123, nil)) // non-string input + assert.NotNil(t, validator("invalid", nil)) // invalid memory size + assert.NotNil(t, validator("", nil)) // empty string +} From 6f40a323c3d8558a081f3575323e0c073c421cda Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 10 Mar 2025 00:32:07 +1300 Subject: [PATCH 13/16] feat: Update minikube cluster schema with memory and CPU type improvements --- minikube/generator/schema_builder.go | 2 ++ minikube/generator/schema_builder_test.go | 2 ++ minikube/resource_cluster.go | 22 +++++++++++----------- minikube/schema_cluster.go | 12 +++++++----- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index 7dd6d04..e01fd06 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -60,6 +60,8 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ Default: "4g", Description: "Amount of RAM to allocate to Kubernetes (format: [], where unit = b, k, m or g). Use \\\"max\\\" to use the maximum amount of memory. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only))", Type: String, + StateFunc: "state_utils.MemoryConverter()", + ValidateDiagFunc: "state_utils.MemoryValidator()", }, "disk_size": { Default: "20000mb", diff --git a/minikube/generator/schema_builder_test.go b/minikube/generator/schema_builder_test.go index 7838157..889d7ec 100644 --- a/minikube/generator/schema_builder_test.go +++ b/minikube/generator/schema_builder_test.go @@ -366,6 +366,8 @@ func TestOverride(t *testing.T) { ForceNew: true, Default: "4g", + StateFunc: state_utils.MemoryConverter(), + ValidateDiagFunc: state_utils.MemoryValidator(), }, } diff --git a/minikube/resource_cluster.go b/minikube/resource_cluster.go index 97739e6..b6e08e1 100644 --- a/minikube/resource_cluster.go +++ b/minikube/resource_cluster.go @@ -271,7 +271,7 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste } memoryStr := d.Get("memory").(string) - memoryMb, err := state_utils.GetMemory(memoryStr); + memoryMb, err := state_utils.GetMemory(memoryStr) if err != nil { return nil, err } @@ -371,16 +371,16 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste vc = lib.ResolveSpecialWaitOptions(vc) cc := config.ClusterConfig{ - Addons: addonConfig, - APIServerPort: d.Get("apiserver_port").(int), - Name: d.Get("cluster_name").(string), - KeepContext: d.Get("keep_context").(bool), - EmbedCerts: d.Get("embed_certs").(bool), - MinikubeISO: state_utils.ReadSliceState(defaultIsos)[0], - KicBaseImage: d.Get("base_image").(string), - Network: d.Get("network").(string), - Memory: memoryMb, - CPUs: func() int { + Addons: addonConfig, + APIServerPort: d.Get("apiserver_port").(int), + Name: d.Get("cluster_name").(string), + KeepContext: d.Get("keep_context").(bool), + EmbedCerts: d.Get("embed_certs").(bool), + MinikubeISO: state_utils.ReadSliceState(defaultIsos)[0], + KicBaseImage: d.Get("base_image").(string), + Network: d.Get("network").(string), + Memory: memoryMb, + CPUs: func() int { cpus, err := state_utils.GetCPUs(d.Get("cpus").(string)) if err != nil { return 2 // Default to 2 CPUs if there's an error diff --git a/minikube/schema_cluster.go b/minikube/schema_cluster.go index 23e5f58..cab0542 100644 --- a/minikube/schema_cluster.go +++ b/minikube/schema_cluster.go @@ -137,7 +137,7 @@ var ( Optional: true, ForceNew: true, - Default: "gcr.io/k8s-minikube/kicbase:v0.0.45@sha256:81df288595202a317b1a4dc2506ca2e4ed5f22373c19a441b88cfbf4b9867c85", + Default: "gcr.io/k8s-minikube/kicbase:v0.0.46@sha256:fd2d445ddcc33ebc5c6b68a17e6219ea207ce63c005095ea1525296da2d1a279", }, "binary_mirror": { @@ -191,13 +191,13 @@ var ( }, "cpus": { - Type: schema.TypeInt, + Type: schema.TypeString, Description: "Number of CPUs allocated to Kubernetes. Use \"max\" to use the maximum number of CPUs. Use \"no-limit\" to not specify a limit (Docker/Podman only)", Optional: true, ForceNew: true, - Default: 2, + Default: "2", }, "cri_socket": { @@ -413,7 +413,7 @@ var ( "gpus": { Type: schema.TypeString, - Description: "Allow pods to use your NVIDIA GPUs. Options include: [all,nvidia] (Docker driver with Docker container-runtime only)", + Description: "Allow pods to use your GPUs. Options include: [all,nvidia,amd] (Docker driver with Docker container-runtime only)", Optional: true, ForceNew: true, @@ -598,7 +598,7 @@ var ( "kubernetes_version": { Type: schema.TypeString, - Description: "The Kubernetes version that the minikube VM will use (ex: v1.2.3, 'stable' for v1.31.0, 'latest' for v1.31.0). Defaults to 'stable'.", + Description: "The Kubernetes version that the minikube VM will use (ex: v1.2.3, 'stable' for v1.32.0, 'latest' for v1.32.0). Defaults to 'stable'.", Optional: true, ForceNew: true, @@ -674,6 +674,8 @@ var ( ForceNew: true, Default: "4g", + StateFunc: state_utils.MemoryConverter(), + ValidateDiagFunc: state_utils.MemoryValidator(), }, "mount": { From 8f98e370aae1b0577c36e51c1f79fd2e7dd925d2 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 10 Mar 2025 00:33:56 +1300 Subject: [PATCH 14/16] test: Add unit tests for memory and CPU configuration scenarios in minikube cluster --- minikube/resource_cluster_test.go | 88 +++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/minikube/resource_cluster_test.go b/minikube/resource_cluster_test.go index 505c356..e590f01 100644 --- a/minikube/resource_cluster_test.go +++ b/minikube/resource_cluster_test.go @@ -326,6 +326,54 @@ func TestClusterCreation_HyperV(t *testing.T) { }) } +func TestClusterNoLimitMemory(t *testing.T) { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterNoLimitMemory", 1, 0, 20000}))}, + Steps: []resource.TestStep{ + { + Config: testUnitClusterNoLimitMemoryConfig("some_driver", "TestClusterNoLimitMemory"), + }, + }, + }) +} + +func TestClusterMaxMemory(t *testing.T) { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterMaxMemory", 1, 0, 20000}))}, + Steps: []resource.TestStep{ + { + Config: testUnitClusterMaxMemoryConfig("some_driver", "TestClusterMaxMemory"), + }, + }, + }) +} + +func TestClusterNoLimitCPU(t *testing.T) { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterNoLimitCPU", 1, 0, 20000}))}, + Steps: []resource.TestStep{ + { + Config: testUnitClusterNoLimitCPUConfig("some_driver", "TestClusterNoLimitCPU"), + }, + }, + }) +} + +func TestClusterMaxCPU(t *testing.T) { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterMaxCPU", 1, 0, 20000}))}, + Steps: []resource.TestStep{ + { + Config: testUnitClusterMaxCPUConfig("some_driver", "TestClusterMaxCPU"), + }, + }, + }) +} + func mockUpdate(props mockClusterClientProperties) schema.ConfigureContextFunc { ctrl := gomock.NewController(props.t) @@ -827,3 +875,43 @@ func testPropertyExists(n string, id string) resource.TestCheckFunc { return nil } } + +func testUnitClusterNoLimitMemoryConfig(driver string, clusterName string) string { + return fmt.Sprintf(` + resource "minikube_cluster" "new" { + driver = "%s" + cluster_name = "%s" + memory = "NoLimit" + } + `, driver, clusterName) +} + +func testUnitClusterMaxMemoryConfig(driver string, clusterName string) string { + return fmt.Sprintf(` + resource "minikube_cluster" "new" { + driver = "%s" + cluster_name = "%s" + memory = "Max" + } + `, driver, clusterName) +} + +func testUnitClusterNoLimitCPUConfig(driver string, clusterName string) string { + return fmt.Sprintf(` + resource "minikube_cluster" "new" { + driver = "%s" + cluster_name = "%s" + cpus = -1 + } + `, driver, clusterName) +} + +func testUnitClusterMaxCPUConfig(driver string, clusterName string) string { + return fmt.Sprintf(` + resource "minikube_cluster" "new" { + driver = "%s" + cluster_name = "%s" + cpus = 0 + } + `, driver, clusterName) +} From e9d726ef0cac6cb36100f303ae0221ce6e585d35 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 10 Mar 2025 00:37:59 +1300 Subject: [PATCH 15/16] test: Update test configurations for memory and CPU limit scenarios --- minikube/resource_cluster_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/minikube/resource_cluster_test.go b/minikube/resource_cluster_test.go index e590f01..c3b19ad 100644 --- a/minikube/resource_cluster_test.go +++ b/minikube/resource_cluster_test.go @@ -881,7 +881,7 @@ func testUnitClusterNoLimitMemoryConfig(driver string, clusterName string) strin resource "minikube_cluster" "new" { driver = "%s" cluster_name = "%s" - memory = "NoLimit" + memory = "no-limit" } `, driver, clusterName) } @@ -891,7 +891,7 @@ func testUnitClusterMaxMemoryConfig(driver string, clusterName string) string { resource "minikube_cluster" "new" { driver = "%s" cluster_name = "%s" - memory = "Max" + memory = "max" } `, driver, clusterName) } @@ -901,7 +901,7 @@ func testUnitClusterNoLimitCPUConfig(driver string, clusterName string) string { resource "minikube_cluster" "new" { driver = "%s" cluster_name = "%s" - cpus = -1 + cpus = "no-limit" } `, driver, clusterName) } @@ -911,7 +911,7 @@ func testUnitClusterMaxCPUConfig(driver string, clusterName string) string { resource "minikube_cluster" "new" { driver = "%s" cluster_name = "%s" - cpus = 0 + cpus = "max" } `, driver, clusterName) } From 19b12d30ad573d5ab795fd3c4c63293ef4520045 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 17 Mar 2025 06:08:06 +1300 Subject: [PATCH 16/16] test: Enhance cluster tests with memory and CPU configurations, integrating new state utilities for validation and conversion --- minikube/generator/schema_builder.go | 8 +- minikube/resource_cluster.go | 32 ++++---- minikube/resource_cluster_test.go | 41 ++++++---- minikube/schema_cluster.go | 2 + minikube/state_utils/cpu.go | 65 ++++++++++++++- minikube/state_utils/cpu_test.go | 115 +++++++++++++++++++++++++++ minikube/state_utils/memory.go | 10 ++- minikube/state_utils/memory_test.go | 18 ----- 8 files changed, 235 insertions(+), 56 deletions(-) diff --git a/minikube/generator/schema_builder.go b/minikube/generator/schema_builder.go index e01fd06..8d60741 100644 --- a/minikube/generator/schema_builder.go +++ b/minikube/generator/schema_builder.go @@ -71,9 +71,11 @@ var schemaOverrides map[string]SchemaOverride = map[string]SchemaOverride{ ValidateDiagFunc: "state_utils.ResourceSizeValidator()", }, "cpus": { - Default: "2", - Description: "Number of CPUs allocated to Kubernetes. Use \\\"max\\\" to use the maximum number of CPUs. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only)", - Type: String, + Default: "2", + Description: "Number of CPUs allocated to Kubernetes. Use \\\"max\\\" to use the maximum number of CPUs. Use \\\"no-limit\\\" to not specify a limit (Docker/Podman only)", + Type: String, + StateFunc: "state_utils.CPUConverter()", + ValidateDiagFunc: "state_utils.CPUValidator()", }, // Customize the description to be the fullset of drivers "driver": { diff --git a/minikube/resource_cluster.go b/minikube/resource_cluster.go index b6e08e1..8f9396c 100644 --- a/minikube/resource_cluster.go +++ b/minikube/resource_cluster.go @@ -276,6 +276,12 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste return nil, err } + cpuStr := d.Get("cpus").(string) + cpus, err := state_utils.GetCPUs(cpuStr) + if err != nil { + return nil, err + } + diskStr := d.Get("disk_size").(string) diskMb, err := pkgutil.CalculateSizeInMB(diskStr) if err != nil { @@ -371,22 +377,16 @@ func initialiseMinikubeClient(d *schema.ResourceData, m interface{}) (lib.Cluste vc = lib.ResolveSpecialWaitOptions(vc) cc := config.ClusterConfig{ - Addons: addonConfig, - APIServerPort: d.Get("apiserver_port").(int), - Name: d.Get("cluster_name").(string), - KeepContext: d.Get("keep_context").(bool), - EmbedCerts: d.Get("embed_certs").(bool), - MinikubeISO: state_utils.ReadSliceState(defaultIsos)[0], - KicBaseImage: d.Get("base_image").(string), - Network: d.Get("network").(string), - Memory: memoryMb, - CPUs: func() int { - cpus, err := state_utils.GetCPUs(d.Get("cpus").(string)) - if err != nil { - return 2 // Default to 2 CPUs if there's an error - } - return cpus - }(), + Addons: addonConfig, + APIServerPort: d.Get("apiserver_port").(int), + Name: d.Get("cluster_name").(string), + KeepContext: d.Get("keep_context").(bool), + EmbedCerts: d.Get("embed_certs").(bool), + MinikubeISO: state_utils.ReadSliceState(defaultIsos)[0], + KicBaseImage: d.Get("base_image").(string), + Network: d.Get("network").(string), + Memory: memoryMb, + CPUs: cpus, DiskSize: diskMb, Driver: driver, ListenAddress: d.Get("listen_address").(string), diff --git a/minikube/resource_cluster_test.go b/minikube/resource_cluster_test.go index c3b19ad..35fe888 100644 --- a/minikube/resource_cluster_test.go +++ b/minikube/resource_cluster_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" + "github.com/scott-the-programmer/terraform-provider-minikube/minikube/state_utils" "github.com/golang/mock/gomock" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -35,12 +36,14 @@ type mockClusterClientProperties struct { haNodes int workerNodes int diskSize int + memory string + cpu string } func TestClusterCreation(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreation", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreation", 1, 0, 20000, "4096mb", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterConfig("some_driver", "TestClusterCreation"), @@ -55,7 +58,7 @@ func TestClusterCreation(t *testing.T) { func TestClusterUpdate(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockUpdate(mockClusterClientProperties{t, "TestClusterUpdate", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockUpdate(mockClusterClientProperties{t, "TestClusterUpdate", 1, 0, 20000, "4096mb", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterConfig("some_driver", "TestClusterUpdate"), @@ -73,7 +76,7 @@ func TestClusterUpdate(t *testing.T) { func TestClusterHA(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationHA", 3, 5, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationHA", 3, 5, 20000, "4096mb", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterHAConfig("some_driver", "TestClusterCreationHA"), @@ -85,7 +88,7 @@ func TestClusterHA(t *testing.T) { func TestClusterDisk(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationDisk", 1, 0, 20480}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationDisk", 1, 0, 20480, "4096mb", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterDiskConfig("some_driver", "TestClusterCreationDisk"), @@ -97,7 +100,7 @@ func TestClusterDisk(t *testing.T) { func TestClusterWait(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationWait", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterCreationWait", 1, 0, 20000, "4096mb", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterWaitConfig("some_driver", "TestClusterCreationWait"), @@ -329,7 +332,7 @@ func TestClusterCreation_HyperV(t *testing.T) { func TestClusterNoLimitMemory(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterNoLimitMemory", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterNoLimitMemory", 1, 0, 20000, "no-limit", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterNoLimitMemoryConfig("some_driver", "TestClusterNoLimitMemory"), @@ -341,7 +344,7 @@ func TestClusterNoLimitMemory(t *testing.T) { func TestClusterMaxMemory(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterMaxMemory", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterMaxMemory", 1, 0, 20000, "max", "1"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterMaxMemoryConfig("some_driver", "TestClusterMaxMemory"), @@ -353,7 +356,7 @@ func TestClusterMaxMemory(t *testing.T) { func TestClusterNoLimitCPU(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterNoLimitCPU", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterNoLimitCPU", 1, 0, 20000, "4096mb", "no-limit"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterNoLimitCPUConfig("some_driver", "TestClusterNoLimitCPU"), @@ -365,7 +368,7 @@ func TestClusterNoLimitCPU(t *testing.T) { func TestClusterMaxCPU(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, - Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterMaxCPU", 1, 0, 20000}))}, + Providers: map[string]*schema.Provider{"minikube": NewProvider(mockSuccess(mockClusterClientProperties{t, "TestClusterMaxCPU", 1, 0, 20000, "4096mb", "max"}))}, Steps: []resource.TestStep{ { Config: testUnitClusterMaxCPUConfig("some_driver", "TestClusterMaxCPU"), @@ -377,7 +380,7 @@ func TestClusterMaxCPU(t *testing.T) { func mockUpdate(props mockClusterClientProperties) schema.ConfigureContextFunc { ctrl := gomock.NewController(props.t) - mockClusterClient := getBaseMockClient(ctrl, props.name, props.haNodes, props.workerNodes, props.diskSize) + mockClusterClient := getBaseMockClient(props.t, ctrl, props.name, props.haNodes, props.workerNodes, props.diskSize, props.memory, props.cpu) gomock.InOrder( mockClusterClient.EXPECT(). @@ -414,7 +417,7 @@ func mockUpdate(props mockClusterClientProperties) schema.ConfigureContextFunc { func mockSuccess(props mockClusterClientProperties) schema.ConfigureContextFunc { ctrl := gomock.NewController(props.t) - mockClusterClient := getBaseMockClient(ctrl, props.name, props.haNodes, props.workerNodes, props.diskSize) + mockClusterClient := getBaseMockClient(props.t, ctrl, props.name, props.haNodes, props.workerNodes, props.diskSize, props.memory, props.cpu) mockClusterClient.EXPECT(). GetAddons(). @@ -432,7 +435,7 @@ func mockSuccess(props mockClusterClientProperties) schema.ConfigureContextFunc return configureContext } -func getBaseMockClient(ctrl *gomock.Controller, clusterName string, haNodes int, workerNodes int, diskSize int) *lib.MockClusterClient { +func getBaseMockClient(t *testing.T, ctrl *gomock.Controller, clusterName string, haNodes int, workerNodes int, diskSize int, memory string, cpu string) *lib.MockClusterClient { mockClusterClient := lib.NewMockClusterClient(ctrl) os.Mkdir("test_output", 0755) @@ -472,6 +475,16 @@ func getBaseMockClient(ctrl *gomock.Controller, clusterName string, haNodes int, Worker: true, } + mem, err := state_utils.GetMemory(memory) + if err != nil { + t.Fatalf("Failed to get memory: %v", err) + } + + c, err := state_utils.GetCPUs(cpu) + if err != nil { + t.Fatalf("Failed to get cpu: %v", err) + } + cc := config.ClusterConfig{ Name: "terraform-provider-minikube-acc", APIServerPort: clusterSchema["apiserver_port"].Default.(int), @@ -480,8 +493,8 @@ func getBaseMockClient(ctrl *gomock.Controller, clusterName string, haNodes int, MinikubeISO: defaultIso, KicBaseImage: clusterSchema["base_image"].Default.(string), Network: clusterSchema["network"].Default.(string), - Memory: 4096, - CPUs: 2, + Memory: mem, + CPUs: c, DiskSize: diskSize, Driver: "some_driver", ListenAddress: clusterSchema["listen_address"].Default.(string), diff --git a/minikube/schema_cluster.go b/minikube/schema_cluster.go index cab0542..031c6c8 100644 --- a/minikube/schema_cluster.go +++ b/minikube/schema_cluster.go @@ -198,6 +198,8 @@ var ( ForceNew: true, Default: "2", + StateFunc: state_utils.CPUConverter(), + ValidateDiagFunc: state_utils.CPUValidator(), }, "cri_socket": { diff --git a/minikube/state_utils/cpu.go b/minikube/state_utils/cpu.go index f8d7cd9..e71b4f8 100644 --- a/minikube/state_utils/cpu.go +++ b/minikube/state_utils/cpu.go @@ -1,10 +1,15 @@ package state_utils import ( - "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" + "errors" + "fmt" "runtime" "strconv" - "fmt" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" ) func GetCPUs(cpuStr string) (int, error) { @@ -22,3 +27,59 @@ func GetCPUs(cpuStr string) (int, error) { } return cpus, nil } + +func CPUConverter() schema.SchemaStateFunc { + return func(val interface{}) string { + result, err := CPUConverterImpl(val) + if err != nil { + panic(err) + } + return result + } +} + +func CPUConverterImpl(val interface{}) (string, error) { + cpuStr, ok := val.(string) + if !ok { + return "", errors.New("cpu value is not a string") + } + + cpus, err := GetCPUs(cpuStr) + if err != nil { + return "", err + } + + return strconv.Itoa(cpus), nil +} + +func CPUValidator() schema.SchemaValidateDiagFunc { + return schema.SchemaValidateDiagFunc(func(val interface{}, path cty.Path) diag.Diagnostics { + err := CPUValidatorImpl(val) + if err != nil { + return diag.FromErr(err) + } + return nil + }) +} + +func CPUValidatorImpl(val interface{}) error { + cpuStr, ok := val.(string) + if !ok { + return errors.New("cpu value is not a string") + } + + if cpuStr == lib.Max || cpuStr == lib.NoLimit { + return nil + } + + cpus, err := strconv.Atoi(cpuStr) + if err != nil { + return fmt.Errorf("invalid CPU value: %v", err) + } + + if cpus <= 0 { + return fmt.Errorf("CPU count must be positive: %d", cpus) + } + + return nil +} diff --git a/minikube/state_utils/cpu_test.go b/minikube/state_utils/cpu_test.go index 767d0d3..1a950ca 100644 --- a/minikube/state_utils/cpu_test.go +++ b/minikube/state_utils/cpu_test.go @@ -2,6 +2,7 @@ package state_utils import ( "runtime" + "strconv" "testing" "github.com/scott-the-programmer/terraform-provider-minikube/minikube/lib" @@ -66,3 +67,117 @@ func TestGetCPUs(t *testing.T) { }) } } + +func TestCPUConverterImpl(t *testing.T) { + tests := []struct { + name string + input interface{} + expected string + expectError bool + }{ + { + name: "valid CPU count", + input: "2", + expected: "2", + expectError: false, + }, + { + name: "max CPUs", + input: lib.Max, + expected: strconv.Itoa(runtime.NumCPU()), + expectError: false, + }, + { + name: "no limit case", + input: lib.NoLimit, + expected: "0", + expectError: false, + }, + { + name: "non-string input", + input: 42, + expected: "", + expectError: true, + }, + { + name: "invalid CPU string", + input: "invalid", + expected: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := CPUConverterImpl(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} + +func TestCPUValidatorImpl(t *testing.T) { + tests := []struct { + name string + input interface{} + expectError bool + }{ + { + name: "valid CPU count", + input: "2", + expectError: false, + }, + { + name: "max CPUs", + input: lib.Max, + expectError: false, + }, + { + name: "no limit case", + input: lib.NoLimit, + expectError: false, + }, + { + name: "non-string input", + input: 42, + expectError: true, + }, + { + name: "invalid CPU string", + input: "invalid", + expectError: true, + }, + { + name: "zero CPU count", + input: "0", + expectError: true, + }, + { + name: "negative CPU count", + input: "-1", + expectError: true, + }, + { + name: "empty string", + input: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := CPUValidatorImpl(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/minikube/state_utils/memory.go b/minikube/state_utils/memory.go index 2e365bf..832eb21 100644 --- a/minikube/state_utils/memory.go +++ b/minikube/state_utils/memory.go @@ -2,6 +2,7 @@ package state_utils import ( "errors" + "strconv" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -58,11 +59,14 @@ func MemoryConverterImpl(val interface{}) (string, error) { return "", errors.New("memory value is not a string") } - if memoryStr == lib.Max || memoryStr == lib.NoLimit { - return memoryStr, nil + mem, err := GetMemory(memoryStr) + if err != nil { + return "", err } - return ResourceSizeConverterImpl(memoryStr) + memoryStr = strconv.Itoa(mem) + "mb" + + return memoryStr, err } func MemoryValidator() schema.SchemaValidateDiagFunc { diff --git a/minikube/state_utils/memory_test.go b/minikube/state_utils/memory_test.go index 519a8dd..fe65d06 100644 --- a/minikube/state_utils/memory_test.go +++ b/minikube/state_utils/memory_test.go @@ -85,18 +85,6 @@ func TestMemoryConverterImpl(t *testing.T) { expected: "1024mb", expectError: false, }, - { - name: "max case", - input: lib.Max, - expected: lib.Max, - expectError: false, - }, - { - name: "no limit case", - input: lib.NoLimit, - expected: lib.NoLimit, - expectError: false, - }, { name: "non-string input", input: 123, @@ -199,10 +187,6 @@ func TestMemoryConverter(t *testing.T) { result := converter("2G") assert.Equal(t, "2048mb", result) - // Test special cases - assert.Equal(t, lib.Max, converter(lib.Max)) - assert.Equal(t, lib.NoLimit, converter(lib.NoLimit)) - // Test panic case assert.Panics(t, func() { converter(123) // non-string input should panic @@ -214,8 +198,6 @@ func TestMemoryValidator(t *testing.T) { // Test valid cases assert.Nil(t, validator("2G", nil)) - assert.Nil(t, validator(lib.Max, nil)) - assert.Nil(t, validator(lib.NoLimit, nil)) // Test invalid cases assert.NotNil(t, validator(123, nil)) // non-string input