From e47a207cf59e47b04ecb640b396075501eccf70b Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 10 Jun 2026 00:10:32 +0530 Subject: [PATCH 1/6] workloadpolicy some field get updated --- internal/provider/workload_policy.go | 42 ++++++++++++ internal/provider/workload_policy_test.go | 78 +++++++++++++++++++++-- 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/internal/provider/workload_policy.go b/internal/provider/workload_policy.go index a503f36..8cc5f08 100644 --- a/internal/provider/workload_policy.go +++ b/internal/provider/workload_policy.go @@ -65,6 +65,8 @@ type WorkloadPolicyResourceModel struct { DriftDeltaPercent types.Float32 `tfsdk:"drift_delta_percent"` MinVpaWindowDataPoints types.Int32 `tfsdk:"min_vpa_window_data_points"` CooldownMinutes types.Int32 `tfsdk:"cooldown_minutes"` + EnablePmaxProtection types.Bool `tfsdk:"enable_pmax_protection"` + PmaxRatioThreshold types.Float32 `tfsdk:"pmax_ratio_threshold"` } type VerticalScalingOptions struct { @@ -78,6 +80,8 @@ type VerticalScalingOptions struct { MaxScaleDownPercent types.Float32 `tfsdk:"max_scale_down_percent"` LimitMultiplier types.Float32 `tfsdk:"limit_multiplier"` MinDataPoints types.Int32 `tfsdk:"min_data_points"` + AdjustReqEvenIfNotSet types.Bool `tfsdk:"adjust_req_even_if_not_set"` + LimitsRemovalEnabled types.Bool `tfsdk:"limits_removal_enabled"` } type HorizontalScalingOptions struct { @@ -156,6 +160,20 @@ func (r *WorkloadPolicyResource) Schema(ctx context.Context, req resource.Schema Computed: true, Default: int32default.StaticInt32(15), }, + "adjust_req_even_if_not_set": schema.BoolAttribute{ + Description: "Recommend requests even when the workload has no existing requests set", + MarkdownDescription: "When true, the recommender will suggest resource requests even if the workload currently has none set. Default: false.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "limits_removal_enabled": schema.BoolAttribute{ + Description: "Actively remove resource limits from workloads", + MarkdownDescription: "When true, the recommender will remove resource limits from workloads (CPU axis only — memory limits removal is not supported). Default: false.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, } } @@ -382,6 +400,20 @@ func (r *WorkloadPolicyResource) Schema(ctx context.Context, req resource.Schema Computed: true, Default: int32default.StaticInt32(30), }, + "enable_pmax_protection": schema.BoolAttribute{ + Description: "Raise requests to cover observed peak usage when the peak/recommendation ratio exceeds pmax_ratio_threshold", + MarkdownDescription: "When true, the recommender raises requests to cover observed peak usage when the peak-to-recommendation ratio exceeds `pmax_ratio_threshold`. Default: false.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "pmax_ratio_threshold": schema.Float32Attribute{ + Description: "Peak-to-recommendation ratio above which pmax protection activates", + MarkdownDescription: "Peak-to-recommendation ratio above which pmax protection activates. Example: 3.0 — triggers when peak is 3× the recommendation. Default: 3.0.", + Optional: true, + Computed: true, + Default: float32default.StaticFloat32(3.0), + }, }, } } @@ -593,6 +625,8 @@ func (m *WorkloadPolicyResourceModel) toProto(ctx context.Context, diags *diag.D DriftDeltaPercent: m.DriftDeltaPercent.ValueFloat32Pointer(), MinVpaWindowDataPoints: m.MinVpaWindowDataPoints.ValueInt32Pointer(), CooldownMinutes: m.CooldownMinutes.ValueInt32Pointer(), + EnablePmaxProtection: m.EnablePmaxProtection.ValueBool(), + PmaxRatioThreshold: m.PmaxRatioThreshold.ValueFloat32Pointer(), } } @@ -676,6 +710,10 @@ func (m *WorkloadPolicyResourceModel) fromProto(policy *apiv1.WorkloadRecommenda if policy.CooldownMinutes != nil { m.CooldownMinutes = types.Int32Value(*policy.CooldownMinutes) } + m.EnablePmaxProtection = types.BoolValue(policy.EnablePmaxProtection) + if policy.PmaxRatioThreshold != nil { + m.PmaxRatioThreshold = types.Float32Value(*policy.PmaxRatioThreshold) + } } func (o *VerticalScalingOptions) toProto() *apiv1.VerticalScalingOptimizationTarget { @@ -693,6 +731,8 @@ func (o *VerticalScalingOptions) toProto() *apiv1.VerticalScalingOptimizationTar MaxScaleDownPercent: o.MaxScaleDownPercent.ValueFloat32Pointer(), LimitMultiplier: o.LimitMultiplier.ValueFloat32Pointer(), MinDataPoints: o.MinDataPoints.ValueInt32Pointer(), + AdjustReqEvenIfNotSet: o.AdjustReqEvenIfNotSet.ValueBool(), + LimitsRemovalEnabled: o.LimitsRemovalEnabled.ValueBool(), } } @@ -733,6 +773,8 @@ func (o *VerticalScalingOptions) fromProto(target *apiv1.VerticalScalingOptimiza if target.MinDataPoints != nil { o.MinDataPoints = types.Int32Value(*target.MinDataPoints) } + o.AdjustReqEvenIfNotSet = types.BoolValue(target.AdjustReqEvenIfNotSet) + o.LimitsRemovalEnabled = types.BoolValue(target.LimitsRemovalEnabled) } func (o *HorizontalScalingOptions) toProto() *apiv1.HorizontalScalingOptimizationTarget { diff --git a/internal/provider/workload_policy_test.go b/internal/provider/workload_policy_test.go index 41872a5..bc1246c 100644 --- a/internal/provider/workload_policy_test.go +++ b/internal/provider/workload_policy_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" @@ -44,6 +45,8 @@ func TestWorkloadPolicyResourceModel(t *testing.T) { MaxScaleDownPercent: types.Float32Value(25.0), LimitMultiplier: types.Float32Value(2.0), MinDataPoints: types.Int32Value(10), + AdjustReqEvenIfNotSet: types.BoolValue(true), + LimitsRemovalEnabled: types.BoolValue(true), } // Test toProto @@ -81,6 +84,26 @@ func TestWorkloadPolicyResourceModel(t *testing.T) { if *proto.MinDataPoints != 10 { t.Errorf("Expected MinDataPoints to be 10, got %d", *proto.MinDataPoints) } + if !proto.AdjustReqEvenIfNotSet { + t.Error("Expected AdjustReqEvenIfNotSet to be true") + } + if !proto.LimitsRemovalEnabled { + t.Error("Expected LimitsRemovalEnabled to be true") + } + }) + + // Test VerticalScalingOptions new fields default to false + t.Run("VerticalScalingOptionsDefaults", func(t *testing.T) { + opts := &VerticalScalingOptions{ + Enabled: types.BoolValue(true), + } + proto := opts.toProto() + if proto.AdjustReqEvenIfNotSet { + t.Error("Expected AdjustReqEvenIfNotSet to default to false") + } + if proto.LimitsRemovalEnabled { + t.Error("Expected LimitsRemovalEnabled to default to false") + } }) // Test HorizontalScalingOptions @@ -123,6 +146,35 @@ func TestWorkloadPolicyResourceModel(t *testing.T) { } }) + // Test pmax protection fields on WorkloadPolicyResourceModel + t.Run("PmaxProtection", func(t *testing.T) { + ctx := context.Background() + var diagnostics diag.Diagnostics + + model := &WorkloadPolicyResourceModel{ + Name: types.StringValue("test"), + Description: types.StringValue(""), + CronSchedule: types.StringValue("*/15 * * * *"), + DefragmentationSchedule: types.StringValue("*/15 * * * *"), + ActionTriggers: types.ListValueMust(types.StringType, nil), + DetectionTriggers: types.ListValueMust(types.StringType, nil), + SchedulerPlugins: types.ListValueMust(types.StringType, nil), + EnablePmaxProtection: types.BoolValue(true), + PmaxRatioThreshold: types.Float32Value(3.0), + } + + proto := model.toProto(ctx, &diagnostics, "team-123") + if proto == nil { + t.Fatal("Expected non-nil proto") + } + if !proto.EnablePmaxProtection { + t.Error("Expected EnablePmaxProtection to be true") + } + if proto.PmaxRatioThreshold == nil || *proto.PmaxRatioThreshold != 3.0 { + t.Errorf("Expected PmaxRatioThreshold to be 3.0, got %v", proto.PmaxRatioThreshold) + } + }) + // Test HPAMetric conversions t.Run("HPAMetricConversions", func(t *testing.T) { opts := &HorizontalScalingOptions{} @@ -160,11 +212,11 @@ func TestWorkloadPolicyResourceModel(t *testing.T) { }) } -func validateSchema(t *testing.T, schema schema.Schema) { +func validateSchema(t *testing.T, s schema.Schema) { // Validate required attributes requiredAttrs := []string{"name", "action_triggers"} for _, attr := range requiredAttrs { - if _, exists := schema.Attributes[attr]; !exists { + if _, exists := s.Attributes[attr]; !exists { t.Errorf("Required attribute %s not found in schema", attr) } } @@ -172,7 +224,7 @@ func validateSchema(t *testing.T, schema schema.Schema) { // Validate computed attributes computedAttrs := []string{"id"} for _, attr := range computedAttrs { - if attrSchema, exists := schema.Attributes[attr]; exists { + if attrSchema, exists := s.Attributes[attr]; exists { if !attrSchema.IsComputed() { t.Errorf("Attribute %s should be computed", attr) } @@ -180,11 +232,27 @@ func validateSchema(t *testing.T, schema schema.Schema) { } // Validate nested attributes exist - if _, exists := schema.Attributes["cpu_vertical_scaling"]; !exists { + if _, exists := s.Attributes["cpu_vertical_scaling"]; !exists { t.Error("cpu_vertical_scaling attribute not found") } - if _, exists := schema.Attributes["horizontal_scaling"]; !exists { + if _, exists := s.Attributes["horizontal_scaling"]; !exists { t.Error("horizontal_scaling attribute not found") } + + // Validate pmax fields exist at top level + for _, attr := range []string{"enable_pmax_protection", "pmax_ratio_threshold"} { + if _, exists := s.Attributes[attr]; !exists { + t.Errorf("Expected top-level attribute %q not found in schema", attr) + } + } + + // Validate new vertical scaling fields exist inside cpu_vertical_scaling + if cpuVs, ok := s.Attributes["cpu_vertical_scaling"].(schema.SingleNestedAttribute); ok { + for _, attr := range []string{"adjust_req_even_if_not_set", "limits_removal_enabled"} { + if _, exists := cpuVs.Attributes[attr]; !exists { + t.Errorf("Expected vertical_scaling attribute %q not found in cpu_vertical_scaling", attr) + } + } + } } From 8777d7e31db8b0915a2ad9dbbf1da3e973e6242c Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 10 Jun 2026 00:57:18 +0530 Subject: [PATCH 2/6] workloadpolicy some field get updated --- .../devzero_workload_policy/resource.tf | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/examples/resources/devzero_workload_policy/resource.tf b/examples/resources/devzero_workload_policy/resource.tf index 92699c7..1c1b1ff 100644 --- a/examples/resources/devzero_workload_policy/resource.tf +++ b/examples/resources/devzero_workload_policy/resource.tf @@ -1,59 +1,46 @@ -# Only required attributes -resource "devzero_workload_policy" "workload_policy" { - name = "terraform-example" +# Minimal — only required attributes +resource "devzero_workload_policy" "minimal" { + name = "cost-saving-policy" action_triggers = ["on_detection", "on_schedule"] } -# All attributes -resource "devzero_workload_policy" "workload_policy" { - name = "terraform-example" - description = "some description" - action_triggers = ["on_detection", "on_schedule"] - cron_schedule = "*/15 * * * *" # Every 15th minute - detection_triggers = ["pod_creation", "pod_update"] - loopback_period_seconds = 3600 # 1 hour - startup_period_seconds = 60 # 1 minute - live_migration_enabled = true - scheduler_plugins = ["dz-scheduler"] - defragmentation_schedule = "*/15 * * * *" +# Full example — values kept in sync with the Pulumi provider +resource "devzero_workload_policy" "cost_saving" { + name = "cost-saving-policy" + description = "Rightsize non-critical workloads" + action_triggers = ["on_detection", "on_schedule"] + cron_schedule = "*/15 * * * *" # every 15 min; required when "on_schedule" is set + detection_triggers = ["pod_creation", "pod_update"] # used when "on_detection" is set + loopback_period_seconds = 86400 # 1 day — lookback window for usage data + cooldown_minutes = 300 # 5 hours between successive scale-down actions + min_data_points = 20 # min samples before any recommendation + min_change_percent = 0.2 # apply only if change > 20% cpu_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = true + enabled = true + target_percentile = 0.75 # P75 of observed usage + min_request = 25 # millicores; hard floor + max_scale_up_percent = 1000 # max % increase per step + max_scale_down_percent = 1 # max % decrease per step + min_data_points = 20 # min CPU samples before recommendation + adjust_req_even_if_not_set = true # set requests even if workload has none + limits_removal_enabled = true # strip CPU limits (cycles compress safely) } memory_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = true + enabled = true + target_percentile = 1 # P100 — guard against OOMKills + min_request = 134217728 # 128 MiB in bytes; hard floor + max_scale_up_percent = 1000 # max % increase per step + max_scale_down_percent = 1 # max % decrease per step + overhead_multiplier = 0.3 # extra headroom over the recommendation + limits_adjustment_enabled = true # adjust limits alongside requests + limit_multiplier = 1 # limits = request × this + min_data_points = 20 # min memory samples before recommendation + adjust_req_even_if_not_set = true # set requests even if workload has none + limits_removal_enabled = false # memory limits removal not supported } - gpu_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = false - } - - gpu_vram_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = false - } - - horizontal_scaling = { - enabled = true - min_replicas = 1 - max_replicas = 2 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = true - } + enable_pmax_protection = true # guard against spike-induced OOMKills + pmax_ratio_threshold = 3 # raise requests when peak is 3× the recommendation } \ No newline at end of file From a27bfb9b0c8f28ceda993bc13ebc98459f9639e2 Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 10 Jun 2026 01:04:57 +0530 Subject: [PATCH 3/6] workloadpolicy some field get updated --- docs/resources/workload_policy.md | 93 +++++++++---------- .../devzero_workload_policy/resource.tf | 44 ++++----- 2 files changed, 67 insertions(+), 70 deletions(-) diff --git a/docs/resources/workload_policy.md b/docs/resources/workload_policy.md index 9560170..ab7c417 100644 --- a/docs/resources/workload_policy.md +++ b/docs/resources/workload_policy.md @@ -13,64 +13,51 @@ Configures DevZero workload recommendation policies, including triggers, scaling ## Example Usage ```terraform -# Only required attributes -resource "devzero_workload_policy" "workload_policy" { - name = "terraform-example" +# Minimal — only required attributes +resource "devzero_workload_policy" "minimal" { + name = "cost-saving-policy" action_triggers = ["on_detection", "on_schedule"] } -# All attributes -resource "devzero_workload_policy" "workload_policy" { - name = "terraform-example" - description = "some description" - action_triggers = ["on_detection", "on_schedule"] - cron_schedule = "*/15 * * * *" # Every 15th minute - detection_triggers = ["pod_creation", "pod_update"] - loopback_period_seconds = 3600 # 1 hour - startup_period_seconds = 60 # 1 minute - live_migration_enabled = true - scheduler_plugins = ["dz-scheduler"] - defragmentation_schedule = "*/15 * * * *" +# Full example — values kept in sync with the Pulumi provider +resource "devzero_workload_policy" "cost_saving" { + name = "cost-saving-policy" + description = "Rightsize non-critical workloads" + action_triggers = ["on_detection", "on_schedule"] + cron_schedule = "*/15 * * * *" # every 15 min; required when "on_schedule" is set + detection_triggers = ["pod_creation", "pod_update"] # used when "on_detection" is set + loopback_period_seconds = 86400 # 1 day — lookback window for usage data + cooldown_minutes = 300 # 5 hours between successive scale-down actions + min_data_points = 20 # min samples before any recommendation + min_change_percent = 0.2 # apply only if change > 20% cpu_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = true + enabled = true + target_percentile = 0.75 # P75 of observed usage + min_request = 25 # millicores; hard floor + max_scale_up_percent = 1000 # max % increase per step + max_scale_down_percent = 1 # max % decrease per step + min_data_points = 20 # min CPU samples before recommendation + adjust_req_even_if_not_set = true # set requests even if workload has none + limits_removal_enabled = true # strip CPU limits (cycles compress safely) } memory_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = true + enabled = true + target_percentile = 1 # P100 — guard against OOMKills + min_request = 134217728 # 128 MiB in bytes; hard floor + max_scale_up_percent = 1000 # max % increase per step + max_scale_down_percent = 1 # max % decrease per step + overhead_multiplier = 0.3 # extra headroom over the recommendation + limits_adjustment_enabled = true # adjust limits alongside requests + limit_multiplier = 1 # limits = request × this + min_data_points = 20 # min memory samples before recommendation + adjust_req_even_if_not_set = true # set requests even if workload has none + limits_removal_enabled = false # memory limits removal not supported } - gpu_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = false - } - - gpu_vram_vertical_scaling = { - enabled = true - min_request = 1000 - max_request = 2000 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = false - } - - horizontal_scaling = { - enabled = true - min_replicas = 1 - max_replicas = 2 - overhead_multiplier = 0.05 # 5% - limits_adjustment_enabled = true - } + enable_pmax_protection = true # guard against spike-induced OOMKills + pmax_ratio_threshold = 3 # raise requests when peak is 3× the recommendation } ``` @@ -91,6 +78,7 @@ resource "devzero_workload_policy" "workload_policy" { - `description` (String) Free-form description of the policy to help others understand its intent and scope. - `detection_triggers` (List of String) Detection triggers for when to apply the workload policy. Only one of `pod_creation` or `pod_update` is allowed.The `pod_creation` trigger is used to apply the workload policy when a pod is created.The `pod_update` trigger is used to apply the workload policy when a pod is updated. - `drift_delta_percent` (Number) Percentage drift from baseline that triggers VPA refresh +- `enable_pmax_protection` (Boolean) When true, the recommender raises requests to cover observed peak usage when the peak-to-recommendation ratio exceeds `pmax_ratio_threshold`. Default: false. - `gpu_vertical_scaling` (Attributes) GPU vertical scaling options (see [below for nested schema](#nestedatt--gpu_vertical_scaling)) - `gpu_vram_vertical_scaling` (Attributes) GPU VRAM vertical scaling options (see [below for nested schema](#nestedatt--gpu_vram_vertical_scaling)) - `horizontal_scaling` (Attributes) Horizontal scaling options (see [below for nested schema](#nestedatt--horizontal_scaling)) @@ -101,6 +89,7 @@ resource "devzero_workload_policy" "workload_policy" { - `min_change_percent` (Number) Global minimum change threshold for applying recommendations - `min_data_points` (Number) Global minimum data points required for recommendations - `min_vpa_window_data_points` (Number) Minimum data points in VPA analysis window +- `pmax_ratio_threshold` (Number) Peak-to-recommendation ratio above which pmax protection activates. Example: 3.0 — triggers when peak is 3× the recommendation. Default: 3.0. - `scheduler_plugins` (List of String) Kubernetes scheduler plugins to activate - `stability_cv_max` (Number) Maximum coefficient of variation to consider stable - `startup_period_seconds` (Number) Startup period seconds of the workload policy. The startup period is the period of time to ignore resource usage data after the workload is started. @@ -114,9 +103,11 @@ resource "devzero_workload_policy" "workload_policy" { Optional: +- `adjust_req_even_if_not_set` (Boolean) When true, the recommender will suggest resource requests even if the workload currently has none set. Default: false. - `enabled` (Boolean) Enable or disable vertical scaling for this resource. When disabled, vertical recommendations will not be applied. - `limit_multiplier` (Number) How much higher limits should be vs requests (e.g., 2.0 = 2x the request). - `limits_adjustment_enabled` (Boolean) Allow recommender to adjust container limits as well as requests. When disabled, only requests are modified. +- `limits_removal_enabled` (Boolean) When true, the recommender will remove resource limits from workloads (CPU axis only — memory limits removal is not supported). Default: false. - `max_request` (Number) Upper bound for container resource requests (e.g., CPU millicores or memory bytes) considered by the recommender. - `max_scale_down_percent` (Number) Maximum percent to scale down in one step - `max_scale_up_percent` (Number) Maximum percent to scale up in one step @@ -131,9 +122,11 @@ Optional: Optional: +- `adjust_req_even_if_not_set` (Boolean) When true, the recommender will suggest resource requests even if the workload currently has none set. Default: false. - `enabled` (Boolean) Enable or disable vertical scaling for this resource. When disabled, vertical recommendations will not be applied. - `limit_multiplier` (Number) How much higher limits should be vs requests (e.g., 2.0 = 2x the request). - `limits_adjustment_enabled` (Boolean) Allow recommender to adjust container limits as well as requests. When disabled, only requests are modified. +- `limits_removal_enabled` (Boolean) When true, the recommender will remove resource limits from workloads (CPU axis only — memory limits removal is not supported). Default: false. - `max_request` (Number) Upper bound for container resource requests (e.g., CPU millicores or memory bytes) considered by the recommender. - `max_scale_down_percent` (Number) Maximum percent to scale down in one step - `max_scale_up_percent` (Number) Maximum percent to scale up in one step @@ -148,9 +141,11 @@ Optional: Optional: +- `adjust_req_even_if_not_set` (Boolean) When true, the recommender will suggest resource requests even if the workload currently has none set. Default: false. - `enabled` (Boolean) Enable or disable vertical scaling for this resource. When disabled, vertical recommendations will not be applied. - `limit_multiplier` (Number) How much higher limits should be vs requests (e.g., 2.0 = 2x the request). - `limits_adjustment_enabled` (Boolean) Allow recommender to adjust container limits as well as requests. When disabled, only requests are modified. +- `limits_removal_enabled` (Boolean) When true, the recommender will remove resource limits from workloads (CPU axis only — memory limits removal is not supported). Default: false. - `max_request` (Number) Upper bound for container resource requests (e.g., CPU millicores or memory bytes) considered by the recommender. - `max_scale_down_percent` (Number) Maximum percent to scale down in one step - `max_scale_up_percent` (Number) Maximum percent to scale up in one step @@ -179,9 +174,11 @@ Optional: Optional: +- `adjust_req_even_if_not_set` (Boolean) When true, the recommender will suggest resource requests even if the workload currently has none set. Default: false. - `enabled` (Boolean) Enable or disable vertical scaling for this resource. When disabled, vertical recommendations will not be applied. - `limit_multiplier` (Number) How much higher limits should be vs requests (e.g., 2.0 = 2x the request). - `limits_adjustment_enabled` (Boolean) Allow recommender to adjust container limits as well as requests. When disabled, only requests are modified. +- `limits_removal_enabled` (Boolean) When true, the recommender will remove resource limits from workloads (CPU axis only — memory limits removal is not supported). Default: false. - `max_request` (Number) Upper bound for container resource requests (e.g., CPU millicores or memory bytes) considered by the recommender. - `max_scale_down_percent` (Number) Maximum percent to scale down in one step - `max_scale_up_percent` (Number) Maximum percent to scale up in one step diff --git a/examples/resources/devzero_workload_policy/resource.tf b/examples/resources/devzero_workload_policy/resource.tf index 1c1b1ff..f74b9c5 100644 --- a/examples/resources/devzero_workload_policy/resource.tf +++ b/examples/resources/devzero_workload_policy/resource.tf @@ -9,36 +9,36 @@ resource "devzero_workload_policy" "cost_saving" { name = "cost-saving-policy" description = "Rightsize non-critical workloads" action_triggers = ["on_detection", "on_schedule"] - cron_schedule = "*/15 * * * *" # every 15 min; required when "on_schedule" is set - detection_triggers = ["pod_creation", "pod_update"] # used when "on_detection" is set - loopback_period_seconds = 86400 # 1 day — lookback window for usage data - cooldown_minutes = 300 # 5 hours between successive scale-down actions - min_data_points = 20 # min samples before any recommendation - min_change_percent = 0.2 # apply only if change > 20% + cron_schedule = "*/15 * * * *" # every 15 min; required when "on_schedule" is set + detection_triggers = ["pod_creation", "pod_update"] # used when "on_detection" is set + loopback_period_seconds = 86400 # 1 day — lookback window for usage data + cooldown_minutes = 300 # 5 hours between successive scale-down actions + min_data_points = 20 # min samples before any recommendation + min_change_percent = 0.2 # apply only if change > 20% cpu_vertical_scaling = { enabled = true - target_percentile = 0.75 # P75 of observed usage - min_request = 25 # millicores; hard floor - max_scale_up_percent = 1000 # max % increase per step - max_scale_down_percent = 1 # max % decrease per step - min_data_points = 20 # min CPU samples before recommendation - adjust_req_even_if_not_set = true # set requests even if workload has none - limits_removal_enabled = true # strip CPU limits (cycles compress safely) + target_percentile = 0.75 # P75 of observed usage + min_request = 25 # millicores; hard floor + max_scale_up_percent = 1000 # max % increase per step + max_scale_down_percent = 1 # max % decrease per step + min_data_points = 20 # min CPU samples before recommendation + adjust_req_even_if_not_set = true # set requests even if workload has none + limits_removal_enabled = true # strip CPU limits (cycles compress safely) } memory_vertical_scaling = { enabled = true - target_percentile = 1 # P100 — guard against OOMKills + target_percentile = 1 # P100 — guard against OOMKills min_request = 134217728 # 128 MiB in bytes; hard floor - max_scale_up_percent = 1000 # max % increase per step - max_scale_down_percent = 1 # max % decrease per step - overhead_multiplier = 0.3 # extra headroom over the recommendation - limits_adjustment_enabled = true # adjust limits alongside requests - limit_multiplier = 1 # limits = request × this - min_data_points = 20 # min memory samples before recommendation - adjust_req_even_if_not_set = true # set requests even if workload has none - limits_removal_enabled = false # memory limits removal not supported + max_scale_up_percent = 1000 # max % increase per step + max_scale_down_percent = 1 # max % decrease per step + overhead_multiplier = 0.3 # extra headroom over the recommendation + limits_adjustment_enabled = true # adjust limits alongside requests + limit_multiplier = 1 # limits = request × this + min_data_points = 20 # min memory samples before recommendation + adjust_req_even_if_not_set = true # set requests even if workload has none + limits_removal_enabled = false # memory limits removal not supported } enable_pmax_protection = true # guard against spike-induced OOMKills From 41b7008cb3f75bfdaf3535be7713392cc687fc8f Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 10 Jun 2026 01:13:55 +0530 Subject: [PATCH 4/6] sync examples for node_policy, node_policy_target, and workload_policy_target with Pulumi docs --- docs/resources/node_policy.md | 66 +++++++++++++++++++ docs/resources/node_policy_target.md | 30 ++++----- docs/resources/workload_policy_target.md | 55 ++++++---------- .../resources/devzero_node_policy/resource.tf | 66 +++++++++++++++++++ .../devzero_node_policy_target/resource.tf | 30 ++++----- .../resource.tf | 55 ++++++---------- 6 files changed, 196 insertions(+), 106 deletions(-) diff --git a/docs/resources/node_policy.md b/docs/resources/node_policy.md index b652d57..4aeeabd 100644 --- a/docs/resources/node_policy.md +++ b/docs/resources/node_policy.md @@ -13,6 +13,72 @@ Manages DevZero node policies for Kubernetes cluster node provisioning and optim ## Example Usage ```terraform +# Standard example — values kept in sync with the Pulumi provider +resource "devzero_node_policy" "standard_nodes" { + name = "standard-nodes" + description = "On-demand x86 nodes for general workloads" + weight = 10 + + capacity_types = { + match_expressions = [{ + key = "capacityTypes" + operator = "In" + values = ["on-demand"] + }] + } + + instance_categories = { + match_expressions = [{ + key = "instanceCategories" + operator = "In" + values = ["m", "c"] + }] + } + + instance_sizes = { + match_expressions = [{ + key = "instanceSizes" + operator = "In" + values = ["large", "xlarge", "2xlarge"] + }] + } + + architectures = { + match_expressions = [{ + key = "architectures" + operator = "In" + values = ["amd64"] + }] + } + + operating_systems = { + match_expressions = [{ + key = "operatingSystems" + operator = "In" + values = ["linux"] + }] + } + + disruption = { + consolidation_policy = "WhenEmptyOrUnderutilized" + consolidate_after = "30m" + expire_after = "168h" # 7 days + } + + aws = { + ami_family = "AL2" + role = "KarpenterNodeRole" + + subnet_selector_terms = [ + { tags = { "karpenter.sh/discovery" = "my-cluster" } } + ] + + security_group_selector_terms = [ + { tags = { "karpenter.sh/discovery" = "my-cluster" } } + ] + } +} + # Minimal example - uses sensible defaults resource "devzero_node_policy" "minimal" { name = "minimal-policy" diff --git a/docs/resources/node_policy_target.md b/docs/resources/node_policy_target.md index 29073c3..7cb8769 100644 --- a/docs/resources/node_policy_target.md +++ b/docs/resources/node_policy_target.md @@ -13,21 +13,27 @@ Attaches a node policy to specific clusters. Node policy targets determine which ## Example Usage ```terraform -# Prerequisites - need cluster and node policy resources +# Prerequisites resource "devzero_cluster" "production" { name = "production-cluster" } -resource "devzero_node_policy" "general" { - name = "general-purpose" - node_pool_name = "general-pool" - node_class_name = "general-class" +resource "devzero_node_policy" "standard_nodes" { + name = "standard-nodes" +} + +# Standard example — values kept in sync with the Pulumi provider +resource "devzero_node_policy_target" "cluster_nodes" { + name = "cluster-nodes" + policy_id = devzero_node_policy.standard_nodes.id + cluster_ids = [devzero_cluster.production.id] + enabled = true } # Minimal example - only required attributes resource "devzero_node_policy_target" "minimal" { name = "production-clusters" - policy_id = devzero_node_policy.general.id + policy_id = devzero_node_policy.standard_nodes.id cluster_ids = [devzero_cluster.production.id] # Defaults applied automatically: @@ -35,18 +41,6 @@ resource "devzero_node_policy_target" "minimal" { # - enabled = true } -# Comprehensive example - all attributes -resource "devzero_node_policy_target" "comprehensive" { - name = "production-general-target" - description = "Applies general purpose node policy to production clusters" - policy_id = devzero_node_policy.general.id - enabled = true - cluster_ids = [ - devzero_cluster.production.id, - # Add more cluster IDs as needed - ] -} - # Example with multiple clusters resource "devzero_cluster" "us_east" { name = "production-us-east-1" diff --git a/docs/resources/workload_policy_target.md b/docs/resources/workload_policy_target.md index 667a93c..0c74d2a 100644 --- a/docs/resources/workload_policy_target.md +++ b/docs/resources/workload_policy_target.md @@ -13,49 +13,34 @@ Defines which workloads a policy applies to by selecting namespaces, workloads, ## Example Usage ```terraform -resource "devzero_cluster" "cluster" { - name = "terraform-example" +resource "devzero_cluster" "production" { + name = "production-cluster" } -resource "devzero_workload_policy" "workload_policy" { - name = "terraform-example" +resource "devzero_workload_policy" "cost_saving" { + name = "cost-saving-policy" } -# Only required attributes -resource "devzero_workload_policy_target" "workload_policy_target" { - name = "terraform-example" - policy_id = devzero_workload_policy.workload_policy.id - cluster_ids = [devzero_cluster.cluster.id] +# Minimal — only required attributes +resource "devzero_workload_policy_target" "minimal" { + name = "production-target" + policy_id = devzero_workload_policy.cost_saving.id + cluster_ids = [devzero_cluster.production.id] } -# All attributes -resource "devzero_workload_policy_target" "workload_policy_target" { - name = "terraform-example" - description = "some description" - policy_id = devzero_workload_policy.workload_policy.id - cluster_ids = [devzero_cluster.cluster.id] - priority = 1 +# Full example — values kept in sync with the Pulumi provider +resource "devzero_workload_policy_target" "production" { + name = "production-target" + policy_id = devzero_workload_policy.cost_saving.id + cluster_ids = [devzero_cluster.production.id] + kind_filter = ["Deployment", "StatefulSet"] enabled = true - workload_names = ["workload-1", "workload-2"] # Empty list means all workloads - node_group_names = ["node-group-1", "node-group-2"] # Empty list means all node groups - kind_filter = ["Deployment", "ReplicaSet"] # Empty list means all kinds - - name_pattern = { - pattern = "terraform-example" - flags = "i" - } - - namespace_selector = { - match_labels = { - app = "terraform-example" - } - } - - workload_selector = { - match_labels = { - app = "terraform-example" - } + # Match namespaces by name pattern — useful when namespaces follow a naming + # convention but aren't consistently labeled (e.g. team-*, prod-*). + namespace_pattern = { + pattern = "^prod-" + flags = "i" # case-insensitive } } ``` diff --git a/examples/resources/devzero_node_policy/resource.tf b/examples/resources/devzero_node_policy/resource.tf index 49a7e94..2b1b15f 100644 --- a/examples/resources/devzero_node_policy/resource.tf +++ b/examples/resources/devzero_node_policy/resource.tf @@ -1,3 +1,69 @@ +# Standard example — values kept in sync with the Pulumi provider +resource "devzero_node_policy" "standard_nodes" { + name = "standard-nodes" + description = "On-demand x86 nodes for general workloads" + weight = 10 + + capacity_types = { + match_expressions = [{ + key = "capacityTypes" + operator = "In" + values = ["on-demand"] + }] + } + + instance_categories = { + match_expressions = [{ + key = "instanceCategories" + operator = "In" + values = ["m", "c"] + }] + } + + instance_sizes = { + match_expressions = [{ + key = "instanceSizes" + operator = "In" + values = ["large", "xlarge", "2xlarge"] + }] + } + + architectures = { + match_expressions = [{ + key = "architectures" + operator = "In" + values = ["amd64"] + }] + } + + operating_systems = { + match_expressions = [{ + key = "operatingSystems" + operator = "In" + values = ["linux"] + }] + } + + disruption = { + consolidation_policy = "WhenEmptyOrUnderutilized" + consolidate_after = "30m" + expire_after = "168h" # 7 days + } + + aws = { + ami_family = "AL2" + role = "KarpenterNodeRole" + + subnet_selector_terms = [ + { tags = { "karpenter.sh/discovery" = "my-cluster" } } + ] + + security_group_selector_terms = [ + { tags = { "karpenter.sh/discovery" = "my-cluster" } } + ] + } +} + # Minimal example - uses sensible defaults resource "devzero_node_policy" "minimal" { name = "minimal-policy" diff --git a/examples/resources/devzero_node_policy_target/resource.tf b/examples/resources/devzero_node_policy_target/resource.tf index 32737f3..cafdbe5 100644 --- a/examples/resources/devzero_node_policy_target/resource.tf +++ b/examples/resources/devzero_node_policy_target/resource.tf @@ -1,18 +1,24 @@ -# Prerequisites - need cluster and node policy resources +# Prerequisites resource "devzero_cluster" "production" { name = "production-cluster" } -resource "devzero_node_policy" "general" { - name = "general-purpose" - node_pool_name = "general-pool" - node_class_name = "general-class" +resource "devzero_node_policy" "standard_nodes" { + name = "standard-nodes" +} + +# Standard example — values kept in sync with the Pulumi provider +resource "devzero_node_policy_target" "cluster_nodes" { + name = "cluster-nodes" + policy_id = devzero_node_policy.standard_nodes.id + cluster_ids = [devzero_cluster.production.id] + enabled = true } # Minimal example - only required attributes resource "devzero_node_policy_target" "minimal" { name = "production-clusters" - policy_id = devzero_node_policy.general.id + policy_id = devzero_node_policy.standard_nodes.id cluster_ids = [devzero_cluster.production.id] # Defaults applied automatically: @@ -20,18 +26,6 @@ resource "devzero_node_policy_target" "minimal" { # - enabled = true } -# Comprehensive example - all attributes -resource "devzero_node_policy_target" "comprehensive" { - name = "production-general-target" - description = "Applies general purpose node policy to production clusters" - policy_id = devzero_node_policy.general.id - enabled = true - cluster_ids = [ - devzero_cluster.production.id, - # Add more cluster IDs as needed - ] -} - # Example with multiple clusters resource "devzero_cluster" "us_east" { name = "production-us-east-1" diff --git a/examples/resources/devzero_workload_policy_target/resource.tf b/examples/resources/devzero_workload_policy_target/resource.tf index 43a0fd6..f51020f 100644 --- a/examples/resources/devzero_workload_policy_target/resource.tf +++ b/examples/resources/devzero_workload_policy_target/resource.tf @@ -1,45 +1,30 @@ -resource "devzero_cluster" "cluster" { - name = "terraform-example" +resource "devzero_cluster" "production" { + name = "production-cluster" } -resource "devzero_workload_policy" "workload_policy" { - name = "terraform-example" +resource "devzero_workload_policy" "cost_saving" { + name = "cost-saving-policy" } -# Only required attributes -resource "devzero_workload_policy_target" "workload_policy_target" { - name = "terraform-example" - policy_id = devzero_workload_policy.workload_policy.id - cluster_ids = [devzero_cluster.cluster.id] +# Minimal — only required attributes +resource "devzero_workload_policy_target" "minimal" { + name = "production-target" + policy_id = devzero_workload_policy.cost_saving.id + cluster_ids = [devzero_cluster.production.id] } -# All attributes -resource "devzero_workload_policy_target" "workload_policy_target" { - name = "terraform-example" - description = "some description" - policy_id = devzero_workload_policy.workload_policy.id - cluster_ids = [devzero_cluster.cluster.id] - priority = 1 +# Full example — values kept in sync with the Pulumi provider +resource "devzero_workload_policy_target" "production" { + name = "production-target" + policy_id = devzero_workload_policy.cost_saving.id + cluster_ids = [devzero_cluster.production.id] + kind_filter = ["Deployment", "StatefulSet"] enabled = true - workload_names = ["workload-1", "workload-2"] # Empty list means all workloads - node_group_names = ["node-group-1", "node-group-2"] # Empty list means all node groups - kind_filter = ["Deployment", "ReplicaSet"] # Empty list means all kinds - - name_pattern = { - pattern = "terraform-example" - flags = "i" - } - - namespace_selector = { - match_labels = { - app = "terraform-example" - } - } - - workload_selector = { - match_labels = { - app = "terraform-example" - } + # Match namespaces by name pattern — useful when namespaces follow a naming + # convention but aren't consistently labeled (e.g. team-*, prod-*). + namespace_pattern = { + pattern = "^prod-" + flags = "i" # case-insensitive } } \ No newline at end of file From 3b950bc6f832b83c28f053dbce2f5a6d9d20fc28 Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 10 Jun 2026 12:24:05 +0530 Subject: [PATCH 5/6] add namespace pattern and also the fix the values in terraform example --- docs/resources/node_policy.md | 73 +------------------ docs/resources/node_policy_target.md | 23 +++--- docs/resources/workload_policy_target.md | 42 +++++++++-- .../resources/devzero_node_policy/resource.tf | 73 +------------------ .../devzero_node_policy_target/resource.tf | 23 +++--- .../resource.tf | 34 +++++++-- internal/provider/workload_policy_target.go | 14 +++- 7 files changed, 107 insertions(+), 175 deletions(-) diff --git a/docs/resources/node_policy.md b/docs/resources/node_policy.md index 4aeeabd..f060171 100644 --- a/docs/resources/node_policy.md +++ b/docs/resources/node_policy.md @@ -13,72 +13,6 @@ Manages DevZero node policies for Kubernetes cluster node provisioning and optim ## Example Usage ```terraform -# Standard example — values kept in sync with the Pulumi provider -resource "devzero_node_policy" "standard_nodes" { - name = "standard-nodes" - description = "On-demand x86 nodes for general workloads" - weight = 10 - - capacity_types = { - match_expressions = [{ - key = "capacityTypes" - operator = "In" - values = ["on-demand"] - }] - } - - instance_categories = { - match_expressions = [{ - key = "instanceCategories" - operator = "In" - values = ["m", "c"] - }] - } - - instance_sizes = { - match_expressions = [{ - key = "instanceSizes" - operator = "In" - values = ["large", "xlarge", "2xlarge"] - }] - } - - architectures = { - match_expressions = [{ - key = "architectures" - operator = "In" - values = ["amd64"] - }] - } - - operating_systems = { - match_expressions = [{ - key = "operatingSystems" - operator = "In" - values = ["linux"] - }] - } - - disruption = { - consolidation_policy = "WhenEmptyOrUnderutilized" - consolidate_after = "30m" - expire_after = "168h" # 7 days - } - - aws = { - ami_family = "AL2" - role = "KarpenterNodeRole" - - subnet_selector_terms = [ - { tags = { "karpenter.sh/discovery" = "my-cluster" } } - ] - - security_group_selector_terms = [ - { tags = { "karpenter.sh/discovery" = "my-cluster" } } - ] - } -} - # Minimal example - uses sensible defaults resource "devzero_node_policy" "minimal" { name = "minimal-policy" @@ -174,10 +108,9 @@ resource "devzero_node_policy" "aws_comprehensive" { # Disruption policy for cost optimization disruption = { - consolidate_after = "15m" - consolidation_policy = "WhenEmptyOrUnderutilized" - expire_after = "720h" # 30 days - ttl_seconds_after_empty = 300 # 5 minutes + consolidate_after = "15m" + consolidation_policy = "WhenEmptyOrUnderutilized" + expire_after = "720h" # 30 days budgets = [ { diff --git a/docs/resources/node_policy_target.md b/docs/resources/node_policy_target.md index 7cb8769..6e991d6 100644 --- a/docs/resources/node_policy_target.md +++ b/docs/resources/node_policy_target.md @@ -22,14 +22,6 @@ resource "devzero_node_policy" "standard_nodes" { name = "standard-nodes" } -# Standard example — values kept in sync with the Pulumi provider -resource "devzero_node_policy_target" "cluster_nodes" { - name = "cluster-nodes" - policy_id = devzero_node_policy.standard_nodes.id - cluster_ids = [devzero_cluster.production.id] - enabled = true -} - # Minimal example - only required attributes resource "devzero_node_policy_target" "minimal" { name = "production-clusters" @@ -41,6 +33,17 @@ resource "devzero_node_policy_target" "minimal" { # - enabled = true } +# Comprehensive example - all attributes +resource "devzero_node_policy_target" "comprehensive" { + name = "cluster-nodes" + description = "Applies standard node policy to production clusters" + policy_id = devzero_node_policy.standard_nodes.id + enabled = true + cluster_ids = [ + devzero_cluster.production.id, + ] +} + # Example with multiple clusters resource "devzero_cluster" "us_east" { name = "production-us-east-1" @@ -57,7 +60,7 @@ resource "devzero_cluster" "eu_west" { resource "devzero_node_policy_target" "multi_cluster" { name = "all-production-clusters" description = "Apply cost optimization policy to all production clusters" - policy_id = devzero_node_policy.general.id + policy_id = devzero_node_policy.standard_nodes.id enabled = true cluster_ids = [ devzero_cluster.us_east.id, @@ -70,7 +73,7 @@ resource "devzero_node_policy_target" "multi_cluster" { resource "devzero_node_policy_target" "disabled" { name = "staging-clusters" description = "Temporarily disabled while testing new policy" - policy_id = devzero_node_policy.general.id + policy_id = devzero_node_policy.standard_nodes.id enabled = false # Target exists but is not active cluster_ids = [devzero_cluster.production.id] } diff --git a/docs/resources/workload_policy_target.md b/docs/resources/workload_policy_target.md index 0c74d2a..5b34bc8 100644 --- a/docs/resources/workload_policy_target.md +++ b/docs/resources/workload_policy_target.md @@ -28,20 +28,40 @@ resource "devzero_workload_policy_target" "minimal" { cluster_ids = [devzero_cluster.production.id] } -# Full example — values kept in sync with the Pulumi provider -resource "devzero_workload_policy_target" "production" { - name = "production-target" +# All attributes +resource "devzero_workload_policy_target" "full" { + name = "terraform-example" + description = "some description" policy_id = devzero_workload_policy.cost_saving.id cluster_ids = [devzero_cluster.production.id] - kind_filter = ["Deployment", "StatefulSet"] + priority = 1 enabled = true - # Match namespaces by name pattern — useful when namespaces follow a naming - # convention but aren't consistently labeled (e.g. team-*, prod-*). + workload_names = ["workload-1", "workload-2"] # Empty list means all workloads + node_group_names = ["node-group-1", "node-group-2"] # Empty list means all node groups + kind_filter = ["Deployment", "StatefulSet"] # Empty list means all kinds + + name_pattern = { + pattern = "terraform-example" + flags = "i" + } + namespace_pattern = { pattern = "^prod-" flags = "i" # case-insensitive } + + namespace_selector = { + match_labels = { + app = "terraform-example" + } + } + + workload_selector = { + match_labels = { + app = "terraform-example" + } + } } ``` @@ -60,6 +80,7 @@ resource "devzero_workload_policy_target" "production" { - `enabled` (Boolean) Enable or disable this target. When disabled, the associated policy will not apply to the selected workloads. - `kind_filter` (List of String) Restrict matching to specific Kubernetes kinds. Allowed values: `Pod`, `Job`, `Deployment`, `StatefulSet`, `DaemonSet`, `ReplicaSet`, `CronJob`, `ReplicationController`, `Rollout`. - `name_pattern` (Attributes) Regex to match workload names. Useful to target rollouts or name conventions (e.g., `^api-.*`). (see [below for nested schema](#nestedatt--name_pattern)) +- `namespace_pattern` (Attributes) Regex to match namespace names. Useful when namespaces follow a naming convention (e.g., `^prod-`). (see [below for nested schema](#nestedatt--namespace_pattern)) - `namespace_selector` (Attributes) Select namespaces by labels. Uses the same semantics as Kubernetes label selectors. (see [below for nested schema](#nestedatt--namespace_selector)) - `node_group_names` (List of String) Restrict matching to specific node groups by name - `priority` (Number) Evaluation priority among multiple targets. Higher values take precedence when multiple targets overlap. @@ -79,6 +100,15 @@ Optional: - `pattern` (String) Regular expression applied to workload names. Uses RE2 syntax. Example: `^api-(staging|prod)-.*$`. + +### Nested Schema for `namespace_pattern` + +Optional: + +- `flags` (String) Regex flags to modify matching behavior. Supported: `i` (case-insensitive), `m` (multi-line). +- `pattern` (String) Regular expression applied to workload names. Uses RE2 syntax. Example: `^api-(staging|prod)-.*$`. + + ### Nested Schema for `namespace_selector` diff --git a/examples/resources/devzero_node_policy/resource.tf b/examples/resources/devzero_node_policy/resource.tf index 2b1b15f..d2e673b 100644 --- a/examples/resources/devzero_node_policy/resource.tf +++ b/examples/resources/devzero_node_policy/resource.tf @@ -1,69 +1,3 @@ -# Standard example — values kept in sync with the Pulumi provider -resource "devzero_node_policy" "standard_nodes" { - name = "standard-nodes" - description = "On-demand x86 nodes for general workloads" - weight = 10 - - capacity_types = { - match_expressions = [{ - key = "capacityTypes" - operator = "In" - values = ["on-demand"] - }] - } - - instance_categories = { - match_expressions = [{ - key = "instanceCategories" - operator = "In" - values = ["m", "c"] - }] - } - - instance_sizes = { - match_expressions = [{ - key = "instanceSizes" - operator = "In" - values = ["large", "xlarge", "2xlarge"] - }] - } - - architectures = { - match_expressions = [{ - key = "architectures" - operator = "In" - values = ["amd64"] - }] - } - - operating_systems = { - match_expressions = [{ - key = "operatingSystems" - operator = "In" - values = ["linux"] - }] - } - - disruption = { - consolidation_policy = "WhenEmptyOrUnderutilized" - consolidate_after = "30m" - expire_after = "168h" # 7 days - } - - aws = { - ami_family = "AL2" - role = "KarpenterNodeRole" - - subnet_selector_terms = [ - { tags = { "karpenter.sh/discovery" = "my-cluster" } } - ] - - security_group_selector_terms = [ - { tags = { "karpenter.sh/discovery" = "my-cluster" } } - ] - } -} - # Minimal example - uses sensible defaults resource "devzero_node_policy" "minimal" { name = "minimal-policy" @@ -159,10 +93,9 @@ resource "devzero_node_policy" "aws_comprehensive" { # Disruption policy for cost optimization disruption = { - consolidate_after = "15m" - consolidation_policy = "WhenEmptyOrUnderutilized" - expire_after = "720h" # 30 days - ttl_seconds_after_empty = 300 # 5 minutes + consolidate_after = "15m" + consolidation_policy = "WhenEmptyOrUnderutilized" + expire_after = "720h" # 30 days budgets = [ { diff --git a/examples/resources/devzero_node_policy_target/resource.tf b/examples/resources/devzero_node_policy_target/resource.tf index cafdbe5..df09938 100644 --- a/examples/resources/devzero_node_policy_target/resource.tf +++ b/examples/resources/devzero_node_policy_target/resource.tf @@ -7,14 +7,6 @@ resource "devzero_node_policy" "standard_nodes" { name = "standard-nodes" } -# Standard example — values kept in sync with the Pulumi provider -resource "devzero_node_policy_target" "cluster_nodes" { - name = "cluster-nodes" - policy_id = devzero_node_policy.standard_nodes.id - cluster_ids = [devzero_cluster.production.id] - enabled = true -} - # Minimal example - only required attributes resource "devzero_node_policy_target" "minimal" { name = "production-clusters" @@ -26,6 +18,17 @@ resource "devzero_node_policy_target" "minimal" { # - enabled = true } +# Comprehensive example - all attributes +resource "devzero_node_policy_target" "comprehensive" { + name = "cluster-nodes" + description = "Applies standard node policy to production clusters" + policy_id = devzero_node_policy.standard_nodes.id + enabled = true + cluster_ids = [ + devzero_cluster.production.id, + ] +} + # Example with multiple clusters resource "devzero_cluster" "us_east" { name = "production-us-east-1" @@ -42,7 +45,7 @@ resource "devzero_cluster" "eu_west" { resource "devzero_node_policy_target" "multi_cluster" { name = "all-production-clusters" description = "Apply cost optimization policy to all production clusters" - policy_id = devzero_node_policy.general.id + policy_id = devzero_node_policy.standard_nodes.id enabled = true cluster_ids = [ devzero_cluster.us_east.id, @@ -55,7 +58,7 @@ resource "devzero_node_policy_target" "multi_cluster" { resource "devzero_node_policy_target" "disabled" { name = "staging-clusters" description = "Temporarily disabled while testing new policy" - policy_id = devzero_node_policy.general.id + policy_id = devzero_node_policy.standard_nodes.id enabled = false # Target exists but is not active cluster_ids = [devzero_cluster.production.id] } diff --git a/examples/resources/devzero_workload_policy_target/resource.tf b/examples/resources/devzero_workload_policy_target/resource.tf index f51020f..cc09f24 100644 --- a/examples/resources/devzero_workload_policy_target/resource.tf +++ b/examples/resources/devzero_workload_policy_target/resource.tf @@ -13,18 +13,38 @@ resource "devzero_workload_policy_target" "minimal" { cluster_ids = [devzero_cluster.production.id] } -# Full example — values kept in sync with the Pulumi provider -resource "devzero_workload_policy_target" "production" { - name = "production-target" +# All attributes +resource "devzero_workload_policy_target" "full" { + name = "terraform-example" + description = "some description" policy_id = devzero_workload_policy.cost_saving.id cluster_ids = [devzero_cluster.production.id] - kind_filter = ["Deployment", "StatefulSet"] + priority = 1 enabled = true - # Match namespaces by name pattern — useful when namespaces follow a naming - # convention but aren't consistently labeled (e.g. team-*, prod-*). + workload_names = ["workload-1", "workload-2"] # Empty list means all workloads + node_group_names = ["node-group-1", "node-group-2"] # Empty list means all node groups + kind_filter = ["Deployment", "StatefulSet"] # Empty list means all kinds + + name_pattern = { + pattern = "terraform-example" + flags = "i" + } + namespace_pattern = { pattern = "^prod-" flags = "i" # case-insensitive } -} \ No newline at end of file + + namespace_selector = { + match_labels = { + app = "terraform-example" + } + } + + workload_selector = { + match_labels = { + app = "terraform-example" + } + } +} diff --git a/internal/provider/workload_policy_target.go b/internal/provider/workload_policy_target.go index b0a17af..f9fe001 100644 --- a/internal/provider/workload_policy_target.go +++ b/internal/provider/workload_policy_target.go @@ -49,8 +49,9 @@ type WorkloadPolicyTargetResourceModel struct { NamespaceSelector *LabelSelector `tfsdk:"namespace_selector"` WorkloadSelector *LabelSelector `tfsdk:"workload_selector"` KindFilter types.List `tfsdk:"kind_filter"` - NamePattern *RegexPattern `tfsdk:"name_pattern"` - WorkloadNames types.List `tfsdk:"workload_names"` + NamePattern *RegexPattern `tfsdk:"name_pattern"` + NamespacePattern *RegexPattern `tfsdk:"namespace_pattern"` + WorkloadNames types.List `tfsdk:"workload_names"` NodeGroupNames types.List `tfsdk:"node_group_names"` ClusterIds types.List `tfsdk:"cluster_ids"` } @@ -201,6 +202,12 @@ func (r *WorkloadPolicyTargetResource) Schema(ctx context.Context, req resource. Optional: true, Attributes: regexPatternAttributes, }, + "namespace_pattern": schema.SingleNestedAttribute{ + Description: "Regex to match namespace names", + MarkdownDescription: "Regex to match namespace names. Useful when namespaces follow a naming convention (e.g., `^prod-`).", + Optional: true, + Attributes: regexPatternAttributes, + }, "workload_names": schema.ListAttribute{ Description: "Explicit list of workload names to include", MarkdownDescription: "Explicit list of workload names to include", @@ -302,6 +309,7 @@ func (r *WorkloadPolicyTargetResource) Create(ctx context.Context, req resource. WorkloadSelector: workloadSelector, KindFilter: kindFilters, NamePattern: data.NamePattern.toProto(), + NamespacePattern: data.NamespacePattern.toProto(), WorkloadNames: workloadNames, NodeGroupNames: nodeGroupNames, ClusterIds: clusterIds, @@ -416,6 +424,7 @@ func (r *WorkloadPolicyTargetResource) Update(ctx context.Context, req resource. WorkloadSelector: workloadSelector, KindFilter: kindFilters, NamePattern: data.NamePattern.toProto(), + NamespacePattern: data.NamespacePattern.toProto(), WorkloadNames: workloadNames, NodeGroupNames: nodeGroupNames, ClusterIds: clusterIds, @@ -631,6 +640,7 @@ func (m *WorkloadPolicyTargetResourceModel) fromProto(target *apiv1.WorkloadPoli m.WorkloadSelector.fromProto(target.WorkloadSelector) m.KindFilter = types.ListValueMust(types.StringType, fromKindFilter(target.KindFilter)) m.NamePattern.fromProto(target.NamePattern) + m.NamespacePattern.fromProto(target.NamespacePattern) m.WorkloadNames = types.ListValueMust(types.StringType, fromStringList(target.WorkloadNames)) m.NodeGroupNames = types.ListValueMust(types.StringType, fromStringList(target.NodeGroupNames)) m.ClusterIds = types.ListValueMust(types.StringType, fromStringList(target.ClusterIds)) From 2d215430559b506bc1653b82f11e2d26e7533058 Mon Sep 17 00:00:00 2001 From: Rupam-It Date: Wed, 10 Jun 2026 12:41:22 +0530 Subject: [PATCH 6/6] all fixed --- internal/provider/workload_policy_target.go | 6 +-- .../provider/workload_policy_target_test.go | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/internal/provider/workload_policy_target.go b/internal/provider/workload_policy_target.go index f9fe001..acf6640 100644 --- a/internal/provider/workload_policy_target.go +++ b/internal/provider/workload_policy_target.go @@ -49,9 +49,9 @@ type WorkloadPolicyTargetResourceModel struct { NamespaceSelector *LabelSelector `tfsdk:"namespace_selector"` WorkloadSelector *LabelSelector `tfsdk:"workload_selector"` KindFilter types.List `tfsdk:"kind_filter"` - NamePattern *RegexPattern `tfsdk:"name_pattern"` - NamespacePattern *RegexPattern `tfsdk:"namespace_pattern"` - WorkloadNames types.List `tfsdk:"workload_names"` + NamePattern *RegexPattern `tfsdk:"name_pattern"` + NamespacePattern *RegexPattern `tfsdk:"namespace_pattern"` + WorkloadNames types.List `tfsdk:"workload_names"` NodeGroupNames types.List `tfsdk:"node_group_names"` ClusterIds types.List `tfsdk:"cluster_ids"` } diff --git a/internal/provider/workload_policy_target_test.go b/internal/provider/workload_policy_target_test.go index af5d066..29006b7 100644 --- a/internal/provider/workload_policy_target_test.go +++ b/internal/provider/workload_policy_target_test.go @@ -307,6 +307,51 @@ func TestWorkloadPolicyTargetResourceModel(t *testing.T) { } }) + // Test NamespacePattern toProto + t.Run("NamespacePattern_ToProto", func(t *testing.T) { + pattern := &RegexPattern{ + Pattern: types.StringValue("^prod-"), + Flags: types.StringValue("i"), + } + + proto := pattern.toProto() + if proto == nil { + t.Fatal("Expected non-nil proto") + } + if proto.Pattern != "^prod-" { + t.Errorf("Expected pattern '^prod-', got %s", proto.Pattern) + } + if proto.Flags != "i" { + t.Errorf("Expected flags 'i', got %s", proto.Flags) + } + }) + + // Test NamespacePattern nil toProto + t.Run("NamespacePattern_NilToProto", func(t *testing.T) { + var pattern *RegexPattern + proto := pattern.toProto() + if proto != nil { + t.Errorf("Expected nil proto for nil NamespacePattern, got %v", proto) + } + }) + + // Test NamespacePattern fromProto + t.Run("NamespacePattern_FromProto", func(t *testing.T) { + protoPattern := &apiv1.RegexPattern{ + Pattern: "^prod-", + Flags: "i", + } + + pattern := &RegexPattern{} + pattern.fromProto(protoPattern) + if pattern.Pattern.ValueString() != "^prod-" { + t.Errorf("Expected pattern '^prod-', got %s", pattern.Pattern.ValueString()) + } + if pattern.Flags.ValueString() != "i" { + t.Errorf("Expected flags 'i', got %s", pattern.Flags.ValueString()) + } + }) + // Test LabelSelector with empty collections returns null t.Run("LabelSelector_EmptyCollectionsToNull", func(t *testing.T) { // Create a proto selector with empty match labels and expressions @@ -454,4 +499,8 @@ func validateTargetSchema(t *testing.T, schema schema.Schema) { if _, exists := schema.Attributes["name_pattern"]; !exists { t.Error("name_pattern attribute not found") } + + if _, exists := schema.Attributes["namespace_pattern"]; !exists { + t.Error("namespace_pattern attribute not found") + } }