From 13d26e1ca19762d6937db65f069fbb1947524339 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Tue, 1 Sep 2020 16:59:33 +0000 Subject: [PATCH] Add rollout option to osconfig patch deployment fixes https://github.com/hashicorp/terraform-provider-google/issues/7124 (#3919) * Add rollout option to osconfig patch deployment * Add test * Set properties to required and correctly specify exactly one of * Fix typos Signed-off-by: Modular Magician --- .changelog/3919.txt | 3 + .../resource_os_config_patch_deployment.go | 190 ++++++++++++++++++ ..._config_patch_deployment_generated_test.go | 7 + .../os_config_patch_deployment.html.markdown | 39 ++++ 4 files changed, 239 insertions(+) create mode 100644 .changelog/3919.txt diff --git a/.changelog/3919.txt b/.changelog/3919.txt new file mode 100644 index 0000000000..b759102537 --- /dev/null +++ b/.changelog/3919.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +osconfig: added rollout field to `google_os_config_patch_deployment` +``` diff --git a/google-beta/resource_os_config_patch_deployment.go b/google-beta/resource_os_config_patch_deployment.go index 4ff99b32ed..91c91d783b 100644 --- a/google-beta/resource_os_config_patch_deployment.go +++ b/google-beta/resource_os_config_patch_deployment.go @@ -853,6 +853,55 @@ A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "201 }, ExactlyOneOf: []string{"one_time_schedule", "recurring_schedule"}, }, + "rollout": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Rollout strategy of the patch job.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disruption_budget": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `The maximum number (or percentage) of VMs per zone to disrupt at any given moment. The number of VMs calculated from multiplying the percentage by the total number of VMs in a zone is rounded up. +During patching, a VM is considered disrupted from the time the agent is notified to begin until patching has completed. This disruption time includes the time to complete reboot and any post-patch steps. +A VM contributes to the disruption budget if its patching operation fails either when applying the patches, running pre or post patch steps, or if it fails to respond with a success notification before timing out. VMs that are not running or do not have an active agent do not count toward this disruption budget. +For zone-by-zone rollouts, if the disruption budget in a zone is exceeded, the patch job stops, because continuing to the next zone requires completion of the patch process in the previous zone. +For example, if the disruption budget has a fixed value of 10, and 8 VMs fail to patch in the current zone, the patch job continues to patch 2 VMs at a time until the zone is completed. When that zone is completed successfully, patching begins with 10 VMs at a time in the next zone. If 10 VMs in the next zone fail to patch, the patch job stops.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fixed": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + Description: `Specifies a fixed value.`, + ExactlyOneOf: []string{"rollout.0.disruption_budget.0.fixed", "rollout.0.disruption_budget.0.percentage"}, + }, + "percentage": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(0, 100), + Description: `Specifies the relative value defined as a percentage, which will be multiplied by a reference value.`, + ExactlyOneOf: []string{"rollout.0.disruption_budget.0.fixed", "rollout.0.disruption_budget.0.percentage"}, + }, + }, + }, + }, + "mode": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"ZONE_BY_ZONE", "CONCURRENT_ZONES"}, false), + Description: `Mode of the patch rollout. Possible values: ["ZONE_BY_ZONE", "CONCURRENT_ZONES"]`, + }, + }, + }, + }, "create_time": { Type: schema.TypeString, Computed: true, @@ -927,6 +976,12 @@ func resourceOSConfigPatchDeploymentCreate(d *schema.ResourceData, meta interfac } else if v, ok := d.GetOkExists("recurring_schedule"); !isEmptyValue(reflect.ValueOf(recurringScheduleProp)) && (ok || !reflect.DeepEqual(v, recurringScheduleProp)) { obj["recurringSchedule"] = recurringScheduleProp } + rolloutProp, err := expandOSConfigPatchDeploymentRollout(d.Get("rollout"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("rollout"); !isEmptyValue(reflect.ValueOf(rolloutProp)) && (ok || !reflect.DeepEqual(v, rolloutProp)) { + obj["rollout"] = rolloutProp + } obj, err = resourceOSConfigPatchDeploymentEncoder(d, meta, obj) if err != nil { @@ -1060,6 +1115,9 @@ func resourceOSConfigPatchDeploymentRead(d *schema.ResourceData, meta interface{ if err := d.Set("recurring_schedule", flattenOSConfigPatchDeploymentRecurringSchedule(res["recurringSchedule"], d, config)); err != nil { return fmt.Errorf("Error reading PatchDeployment: %s", err) } + if err := d.Set("rollout", flattenOSConfigPatchDeploymentRollout(res["rollout"], d, config)); err != nil { + return fmt.Errorf("Error reading PatchDeployment: %s", err) + } return nil } @@ -1905,6 +1963,74 @@ func flattenOSConfigPatchDeploymentRecurringScheduleMonthlyMonthDay(v interface{ return v // let terraform core handle it otherwise } +func flattenOSConfigPatchDeploymentRollout(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["mode"] = + flattenOSConfigPatchDeploymentRolloutMode(original["mode"], d, config) + transformed["disruption_budget"] = + flattenOSConfigPatchDeploymentRolloutDisruptionBudget(original["disruptionBudget"], d, config) + return []interface{}{transformed} +} +func flattenOSConfigPatchDeploymentRolloutMode(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOSConfigPatchDeploymentRolloutDisruptionBudget(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["fixed"] = + flattenOSConfigPatchDeploymentRolloutDisruptionBudgetFixed(original["fixed"], d, config) + transformed["percentage"] = + flattenOSConfigPatchDeploymentRolloutDisruptionBudgetPercentage(original["percentage"], d, config) + return []interface{}{transformed} +} +func flattenOSConfigPatchDeploymentRolloutDisruptionBudgetFixed(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenOSConfigPatchDeploymentRolloutDisruptionBudgetPercentage(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + func expandOSConfigPatchDeploymentDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } @@ -3052,6 +3178,70 @@ func expandOSConfigPatchDeploymentRecurringScheduleMonthlyMonthDay(v interface{} return v, nil } +func expandOSConfigPatchDeploymentRollout(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedMode, err := expandOSConfigPatchDeploymentRolloutMode(original["mode"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMode); val.IsValid() && !isEmptyValue(val) { + transformed["mode"] = transformedMode + } + + transformedDisruptionBudget, err := expandOSConfigPatchDeploymentRolloutDisruptionBudget(original["disruption_budget"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDisruptionBudget); val.IsValid() && !isEmptyValue(val) { + transformed["disruptionBudget"] = transformedDisruptionBudget + } + + return transformed, nil +} + +func expandOSConfigPatchDeploymentRolloutMode(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOSConfigPatchDeploymentRolloutDisruptionBudget(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedFixed, err := expandOSConfigPatchDeploymentRolloutDisruptionBudgetFixed(original["fixed"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFixed); val.IsValid() && !isEmptyValue(val) { + transformed["fixed"] = transformedFixed + } + + transformedPercentage, err := expandOSConfigPatchDeploymentRolloutDisruptionBudgetPercentage(original["percentage"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPercentage); val.IsValid() && !isEmptyValue(val) { + transformed["percentage"] = transformedPercentage + } + + return transformed, nil +} + +func expandOSConfigPatchDeploymentRolloutDisruptionBudgetFixed(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOSConfigPatchDeploymentRolloutDisruptionBudgetPercentage(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func resourceOSConfigPatchDeploymentEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { schedule := obj["recurringSchedule"].(map[string]interface{}) if schedule["monthly"] != nil { diff --git a/google-beta/resource_os_config_patch_deployment_generated_test.go b/google-beta/resource_os_config_patch_deployment_generated_test.go index 5a66d93a81..0abe249bf9 100644 --- a/google-beta/resource_os_config_patch_deployment_generated_test.go +++ b/google-beta/resource_os_config_patch_deployment_generated_test.go @@ -286,6 +286,13 @@ resource "google_os_config_patch_deployment" "patch" { } } } + + rollout { + mode = "ZONE_BY_ZONE" + disruption_budget { + fixed = 1 + } + } } `, context) } diff --git a/website/docs/r/os_config_patch_deployment.html.markdown b/website/docs/r/os_config_patch_deployment.html.markdown index 9e10fd7ad6..4d3a25ea54 100644 --- a/website/docs/r/os_config_patch_deployment.html.markdown +++ b/website/docs/r/os_config_patch_deployment.html.markdown @@ -237,6 +237,13 @@ resource "google_os_config_patch_deployment" "patch" { } } } + + rollout { + mode = "ZONE_BY_ZONE" + disruption_budget { + fixed = 1 + } + } } ``` @@ -320,6 +327,11 @@ The `group_labels` block supports: Schedule recurring executions. Structure is documented below. +* `rollout` - + (Optional) + Rollout strategy of the patch job. + Structure is documented below. + * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. @@ -733,6 +745,33 @@ The `week_day_of_month` block supports: A day of the week. Possible values are `MONDAY`, `TUESDAY`, `WEDNESDAY`, `THURSDAY`, `FRIDAY`, `SATURDAY`, and `SUNDAY`. +The `rollout` block supports: + +* `mode` - + (Required) + Mode of the patch rollout. + Possible values are `ZONE_BY_ZONE` and `CONCURRENT_ZONES`. + +* `disruption_budget` - + (Required) + The maximum number (or percentage) of VMs per zone to disrupt at any given moment. The number of VMs calculated from multiplying the percentage by the total number of VMs in a zone is rounded up. + During patching, a VM is considered disrupted from the time the agent is notified to begin until patching has completed. This disruption time includes the time to complete reboot and any post-patch steps. + A VM contributes to the disruption budget if its patching operation fails either when applying the patches, running pre or post patch steps, or if it fails to respond with a success notification before timing out. VMs that are not running or do not have an active agent do not count toward this disruption budget. + For zone-by-zone rollouts, if the disruption budget in a zone is exceeded, the patch job stops, because continuing to the next zone requires completion of the patch process in the previous zone. + For example, if the disruption budget has a fixed value of 10, and 8 VMs fail to patch in the current zone, the patch job continues to patch 2 VMs at a time until the zone is completed. When that zone is completed successfully, patching begins with 10 VMs at a time in the next zone. If 10 VMs in the next zone fail to patch, the patch job stops. + Structure is documented below. + + +The `disruption_budget` block supports: + +* `fixed` - + (Optional) + Specifies a fixed value. + +* `percentage` - + (Optional) + Specifies the relative value defined as a percentage, which will be multiplied by a reference value. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: