From 7ea66ea4f4e5d64a2ff9c3e6e60e7d004fdf8399 Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Tue, 23 Jun 2026 19:01:17 +0530 Subject: [PATCH 1/2] feat: add kubelet configuration support for Azure node policy This adds kubelet as a nested attribute under azure in the node policy schema, exposing fields like cpu_manager_policy, cpu_cfs_quota, image_gc_*, topology_manager_policy, allowed_unsafe_sysctls, container_log_max_*, and pod_pids_limit. Tests cover model validation and schema regression for all kubelet sub-attributes --- internal/provider/node_policy.go | 47 +++++++++++++++++ internal/provider/node_policy_test.go | 73 +++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/internal/provider/node_policy.go b/internal/provider/node_policy.go index f4428f1..18aeb17 100644 --- a/internal/provider/node_policy.go +++ b/internal/provider/node_policy.go @@ -616,6 +616,53 @@ func (r *NodePolicyResource) Schema(ctx context.Context, req resource.SchemaRequ Description: "Maximum number of pods per node", Optional: true, }, + "kubelet": schema.SingleNestedAttribute{ + Description: "Azure kubelet configuration overrides", + Optional: true, + Attributes: map[string]schema.Attribute{ + "cpu_manager_policy": schema.StringAttribute{ + Description: "CPU manager policy (None, static)", + Optional: true, + }, + "cpu_cfs_quota": schema.BoolAttribute{ + Description: "Enable CPU CFS quota enforcement", + Optional: true, + }, + "cpu_cfs_quota_period": schema.StringAttribute{ + Description: "CPU CFS quota period", + Optional: true, + }, + "image_gc_high_threshold_percent": schema.Int32Attribute{ + Description: "Image GC high threshold percent", + Optional: true, + }, + "image_gc_low_threshold_percent": schema.Int32Attribute{ + Description: "Image GC low threshold percent", + Optional: true, + }, + "topology_manager_policy": schema.StringAttribute{ + Description: "Topology manager policy", + Optional: true, + }, + "allowed_unsafe_sysctls": schema.ListAttribute{ + Description: "List of allowed unsafe sysctls", + Optional: true, + ElementType: types.StringType, + }, + "container_log_max_size": schema.StringAttribute{ + Description: "Maximum container log file size", + Optional: true, + }, + "container_log_max_files": schema.Int32Attribute{ + Description: "Maximum number of container log files", + Optional: true, + }, + "pod_pids_limit": schema.Int64Attribute{ + Description: "Maximum number of PIDs per pod", + Optional: true, + }, + }, + }, }, }, // Raw Karpenter specs diff --git a/internal/provider/node_policy_test.go b/internal/provider/node_policy_test.go index 50adefc..ef658b1 100644 --- a/internal/provider/node_policy_test.go +++ b/internal/provider/node_policy_test.go @@ -154,6 +154,18 @@ func TestNodePolicyResourceModel(t *testing.T) { FipsMode: types.StringValue("Disabled"), Tags: types.MapValueMust(types.StringType, map[string]attr.Value{"Environment": types.StringValue("production")}), MaxPods: types.Int32Value(110), + Kubelet: &AzureKubeletConfiguration{ + CpuManagerPolicy: types.StringValue("static"), + CpuCfsQuota: types.BoolValue(true), + CpuCfsQuotaPeriod: types.StringValue("100ms"), + ImageGcHighThresholdPercent: types.Int32Value(85), + ImageGcLowThresholdPercent: types.Int32Value(70), + TopologyManagerPolicy: types.StringValue("restricted"), + AllowedUnsafeSysctls: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("net.ipv4.tcp_syncookies")}), + ContainerLogMaxSize: types.StringValue("50Mi"), + ContainerLogMaxFiles: types.Int32Value(5), + PodPidsLimit: types.Int64Value(4096), + }, } if azureConfig.VnetSubnetId.ValueString() == "" { @@ -168,6 +180,18 @@ func TestNodePolicyResourceModel(t *testing.T) { if azureConfig.MaxPods.ValueInt32() != 110 { t.Errorf("Expected max_pods to be 110, got %d", azureConfig.MaxPods.ValueInt32()) } + if azureConfig.Kubelet == nil { + t.Fatal("Expected Kubelet to be non-nil") + } + if azureConfig.Kubelet.CpuManagerPolicy.ValueString() != "static" { + t.Errorf("Expected CpuManagerPolicy to be 'static', got %s", azureConfig.Kubelet.CpuManagerPolicy.ValueString()) + } + if !azureConfig.Kubelet.CpuCfsQuota.ValueBool() { + t.Error("Expected CpuCfsQuota to be true") + } + if azureConfig.Kubelet.PodPidsLimit.ValueInt64() != 4096 { + t.Errorf("Expected PodPidsLimit to be 4096, got %d", azureConfig.Kubelet.PodPidsLimit.ValueInt64()) + } }) // Test RawKarpenterSpec @@ -1014,6 +1038,10 @@ func TestNodePolicyResourceModel(t *testing.T) { }) } +// singleNestedSchema is a local alias to allow type-asserting schema.Attribute +// to access nested Attributes without importing internal framework packages. +type singleNestedSchema = schema.SingleNestedAttribute + func validateNodePolicySchema(t *testing.T, schema schema.Schema) { // Validate required attributes requiredAttrs := []string{"name"} @@ -1070,4 +1098,49 @@ func validateNodePolicySchema(t *testing.T, schema schema.Schema) { if _, exists := schema.Attributes["azure"]; !exists { t.Error("Azure configuration not found in schema") } + + // Validate azure nested attributes include kubelet (regression test for missing kubelet schema bug) + azureRaw, ok := schema.Attributes["azure"] + if !ok { + t.Fatal("azure attribute not found in schema") + } + azureSingle, ok := azureRaw.(singleNestedSchema) + if !ok { + t.Fatal("azure attribute is not a SingleNestedAttribute") + } + azureKubeletFields := []string{ + "kubelet", + "vnet_subnet_id", + "os_disk_size_gb", + "image_family", + "fips_mode", + "tags", + "max_pods", + } + for _, field := range azureKubeletFields { + if _, exists := azureSingle.Attributes[field]; !exists { + t.Errorf("azure schema is missing '%s' attribute", field) + } + } + + // Validate kubelet sub-attributes + kubeletRaw, ok := azureSingle.Attributes["kubelet"] + if !ok { + t.Fatal("azure schema is missing 'kubelet' attribute") + } + kubeletSingle, ok := kubeletRaw.(singleNestedSchema) + if !ok { + t.Fatal("azure.kubelet is not a SingleNestedAttribute") + } + kubeletFields := []string{ + "cpu_manager_policy", "cpu_cfs_quota", "cpu_cfs_quota_period", + "image_gc_high_threshold_percent", "image_gc_low_threshold_percent", + "topology_manager_policy", "allowed_unsafe_sysctls", + "container_log_max_size", "container_log_max_files", "pod_pids_limit", + } + for _, field := range kubeletFields { + if _, exists := kubeletSingle.Attributes[field]; !exists { + t.Errorf("azure.kubelet schema is missing '%s' attribute", field) + } + } } From 2cc1fe62a1d52245fa48ad4112355663b4b454e2 Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 24 Jun 2026 10:06:29 +0530 Subject: [PATCH 2/2] docs for azure kubelet added --- docs/resources/node_policy.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/resources/node_policy.md b/docs/resources/node_policy.md index a9fd91f..812c39e 100644 --- a/docs/resources/node_policy.md +++ b/docs/resources/node_policy.md @@ -442,11 +442,29 @@ Optional: - `fips_mode` (String) FIPS 140-2 mode. Valid values: `FIPS`, `Disabled`. - `image_family` (String) Azure image family. Valid values: `Ubuntu`, `Ubuntu2204`, `Ubuntu2404`, `AzureLinux`. +- `kubelet` (Attributes) Azure kubelet configuration overrides (see [below for nested schema](#nestedatt--azure--kubelet)) - `max_pods` (Number) Maximum number of pods per node - `os_disk_size_gb` (Number) OS disk size in GB - `tags` (Map of String) Azure tags to apply to resources - `vnet_subnet_id` (String) VNet subnet ID + +### Nested Schema for `azure.kubelet` + +Optional: + +- `allowed_unsafe_sysctls` (List of String) List of allowed unsafe sysctls +- `container_log_max_files` (Number) Maximum number of container log files +- `container_log_max_size` (String) Maximum container log file size +- `cpu_cfs_quota` (Boolean) Enable CPU CFS quota enforcement +- `cpu_cfs_quota_period` (String) CPU CFS quota period +- `cpu_manager_policy` (String) CPU manager policy (None, static) +- `image_gc_high_threshold_percent` (Number) Image GC high threshold percent +- `image_gc_low_threshold_percent` (Number) Image GC low threshold percent +- `pod_pids_limit` (Number) Maximum number of PIDs per pod +- `topology_manager_policy` (String) Topology manager policy + + ### Nested Schema for `capacity_types`