diff --git a/mmv1/products/gkehub2/RolloutSequence.yaml b/mmv1/products/gkehub2/RolloutSequence.yaml index 79748521aafd..52b236e1a45d 100644 --- a/mmv1/products/gkehub2/RolloutSequence.yaml +++ b/mmv1/products/gkehub2/RolloutSequence.yaml @@ -40,6 +40,11 @@ async: base_url: '{{op_id}}' result: resource_inside_response: false +custom_code: + constants: 'templates/terraform/constants/gkehub2_rollout_sequence.go.tmpl' + post_create: 'templates/terraform/post_create/gkehub2_rollout_sequence.go.tmpl' + post_create_failure: 'templates/terraform/post_create_failure/delete_on_failure.go.tmpl' + post_update: 'templates/terraform/post_update/gkehub2_rollout_sequence.go.tmpl' parameters: - name: 'rollout_sequence_id' type: String @@ -157,3 +162,61 @@ properties: Current valid values are `CONTROL_PLANE_MINOR`, `CONTROL_PLANE_PATCH`, `NODE_MINOR`, and `NODE_PATCH`. item_type: type: String + + - name: 'targetControlPlaneVersion' + type: String + description: | + The current target control plane version. + output: true + - name: 'targetNodeVersion' + type: String + description: | + The current target node version. + output: true + - name: 'operationalState' + type: NestedObject + description: | + The operational state of the rollout sequence. + output: true + properties: + - name: 'state' + type: String + description: | + The state of the rollout sequence. + output: true + +virtual_fields: + - name: 'min_control_plane_version' + type: String + description: | + Minimum control plane version that the clusters in the sequence should be upgraded to. + Setting this field will cause the creation of a rollout to the specified version. + Any rollout of the same type already running on the first stage of the sequence will be cancelled to allow for the creation of the new rollout. + Should be a valid [semantic version](https://semver.org/). + Version aliases are supported, as described in the [cluster version docs](https://docs.cloud.google.com/kubernetes-engine/versioning#specifying_cluster_version). + Supported formats: `1.X`, `1.X.Y`, `1.X.Y-gke.N`. + - name: 'min_node_version' + type: String + description: | + Minimum node version that the clusters in the sequence should be upgraded to. + Setting this field will cause the creation of a rollout to the specified version. + Any rollout of the same type already running on the first stage of the sequence will be cancelled to allow for the creation of the new rollout. + Should be a valid [semantic version](https://semver.org/). + Version aliases are supported, as described in the [cluster version docs](https://docs.cloud.google.com/kubernetes-engine/versioning#specifying_cluster_version). + Supported formats: `1.X`, `1.X.Y`, `1.X.Y-gke.N`. + +samples: + - name: 'gke_hub_rollout_sequence' + primary_resource_id: 'rollout_sequence' + min_version: beta + steps: + - name: 'gke_hub_rollout_sequence_basic' + resource_id_vars: + rollout_sequence_id: 'rs-basic' + test_env_vars: + project_id: 'PROJECT_NAME' + - name: 'gke_hub_rollout_sequence_update' + resource_id_vars: + rollout_sequence_id: 'rs-basic' + test_env_vars: + project_id: 'PROJECT_NAME' diff --git a/mmv1/templates/terraform/constants/gkehub2_rollout_sequence.go.tmpl b/mmv1/templates/terraform/constants/gkehub2_rollout_sequence.go.tmpl new file mode 100644 index 000000000000..f1e08e01e59a --- /dev/null +++ b/mmv1/templates/terraform/constants/gkehub2_rollout_sequence.go.tmpl @@ -0,0 +1,199 @@ +import ( + "github.com/hashicorp/go-version" +) + +// Compares two GKE versions using the standard go-version package. +// Returns true if minVer is strictly greater than targetVer. +// Returns true if targetVer is empty (representing a new rollout sequence). +// Returns false if minVer is empty (representing an unset min_*_version field). +func shouldUpgradeRolloutSequence(minVer, targetVer string) (bool, error) { + if minVer == "" { + return false, nil + } + minV, err := version.NewVersion(minVer) + if err != nil { + return false, fmt.Errorf("invalid min version format %q: %w", minVer, err) + } + + if targetVer == "" { + return true, nil + } + targetV, err := version.NewVersion(targetVer) + if err != nil { + return false, fmt.Errorf("invalid target version format %q: %w", targetVer, err) + } + + return minV.GreaterThan(targetV), nil +} + +// Polls the state of a RolloutSequence resource, until it enters a state other +// than INITIALIZING or until the 1h timeout passes. +func pollSequenceInitialization(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}GKEHub2BasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/locations/global/rolloutSequences/{{"{{"}}rollout_sequence_id{{"}}"}}") + if err != nil { + return err + } + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("failed to fetch project for RolloutSequence: %w", err) + } + billingProject := project + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + pollRead := func() (map[string]interface{}, error) { + return transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + } + + checkResponse := func(resp map[string]interface{}, respErr error) transport_tpg.PollResult { + if respErr != nil { + return transport_tpg.ErrorPollResult(respErr) + } + if resp == nil { + return transport_tpg.PendingStatusPollResult("nil response") + } + + opStateRaw, ok := resp["operationalState"] + if !ok || opStateRaw == nil { + return transport_tpg.PendingStatusPollResult("operationalState missing") + } + + opState, ok := opStateRaw.(map[string]interface{}) + if !ok { + return transport_tpg.ErrorPollResult(fmt.Errorf("operationalState is not a map")) + } + + stateRaw, ok := opState["state"] + if !ok || stateRaw == nil { + return transport_tpg.PendingStatusPollResult("state missing") + } + state, ok := stateRaw.(string) + if !ok { + return transport_tpg.ErrorPollResult(fmt.Errorf("state is not a string")) + } + + if state == "INITIALIZING" { + return transport_tpg.PendingStatusPollResult("INITIALIZING") + } + + return transport_tpg.SuccessPollResult() + } + + return transport_tpg.PollingWaitTime(pollRead, checkResponse, "Polling RolloutSequence initialization", 1*time.Hour, 1) +} + +// Fetches the current targetControlPlaneVersion and targetNodeVersion values of the RolloutSequence resource. +func fetchCurrentTargetVersions(d *schema.ResourceData, meta interface{}) (string, string, error) { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return "", "", err + } + project, err := tpgresource.GetProject(d, config) + if err != nil { + return "", "", fmt.Errorf("failed to fetch project for RolloutSequence: %w", err) + } + billingProject := project + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}GKEHub2BasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/locations/global/rolloutSequences/{{"{{"}}rollout_sequence_id{{"}}"}}") + if err != nil { + return "", "", err + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return "", "", fmt.Errorf("failed to fetch latest RolloutSequence state: %w", err) + } + + targetCPVer := "" + if v, ok := res["targetControlPlaneVersion"]; ok && v != nil { + if s, ok := v.(string); ok { + targetCPVer = s + } else { + return "", "", fmt.Errorf("expected targetControlPlaneVersion to be a string, got %T", v) + } + } + targetNodeVer := "" + if v, ok := res["targetNodeVersion"]; ok && v != nil { + if s, ok := v.(string); ok { + targetNodeVer = s + } else { + return "", "", fmt.Errorf("expected targetNodeVersion to be a string, got %T", v) + } + } + + return targetCPVer, targetNodeVer, nil +} + +{{- if ne $.ProductMetadata.Compiler "terraformgoogleconversion-codegen" }} +// Makes an UpgradeRolloutSequence call for the provided upgradeType and version. +func triggerUpgradeRolloutSequence(d *schema.ResourceData, meta interface{}, upgradeType string, version string) error { + config := meta.(*transport_tpg.Config) + url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}GKEHub2BasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/locations/global/rolloutSequences/{{"{{"}}rollout_sequence_id{{"}}:upgrade"}}") + if err != nil { + return err + } + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("failed to fetch project for RolloutSequence: %w", err) + } + billingProject := project + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + body := map[string]interface{}{ + "upgradeType": upgradeType, + "version": version, + "force": true, + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: body, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + if err != nil { + return fmt.Errorf("failed to trigger RolloutSequence upgrade (%s): %w", upgradeType, err) + } + + err = GKEHub2OperationWaitTime( + config, res, project, "Triggering RolloutSequence Upgrade", userAgent, + d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return fmt.Errorf("failed to wait for RolloutSequence upgrade (%s): %w", upgradeType, err) + } + + return nil +} +{{- end }} \ No newline at end of file diff --git a/mmv1/templates/terraform/post_create/gkehub2_rollout_sequence.go.tmpl b/mmv1/templates/terraform/post_create/gkehub2_rollout_sequence.go.tmpl new file mode 100644 index 000000000000..29a3e4cd3bc9 --- /dev/null +++ b/mmv1/templates/terraform/post_create/gkehub2_rollout_sequence.go.tmpl @@ -0,0 +1,55 @@ +if err := func() error { + var minCPVer string + if rawCP, ok := d.GetOk("min_control_plane_version"); ok { + minCPVer = rawCP.(string) + } + + var minNodeVer string + if rawNode, ok := d.GetOk("min_node_version"); ok { + minNodeVer = rawNode.(string) + } + + if minCPVer == "" && minNodeVer == "" { + return nil + } + + log.Printf("[DEBUG] Polling RolloutSequence initialization.") + if err := pollSequenceInitialization(d, meta); err != nil { + return err + } + + log.Printf("[DEBUG] Fetching current RolloutSequence target versions.") + targetCPVer, targetNodeVer, err := fetchCurrentTargetVersions(d, meta) + if err != nil { + return err + } + + if minCPVer != "" { + shouldUpgrade, err := shouldUpgradeRolloutSequence(minCPVer, targetCPVer) + if err != nil { + return err + } + if shouldUpgrade { + log.Printf("[DEBUG] min_control_plane_version (%s) specified on RolloutSequence creation. Upgrading RolloutSequence.", minCPVer) + if err := triggerUpgradeRolloutSequence(d, meta, "CONTROL_PLANE", minCPVer); err != nil { + return err + } + } + } + if minNodeVer != "" { + shouldUpgrade, err := shouldUpgradeRolloutSequence(minNodeVer, targetNodeVer) + if err != nil { + return err + } + if shouldUpgrade { + log.Printf("[DEBUG] min_node_version (%s) specified on RolloutSequence creation. Upgrading RolloutSequence.", minNodeVer) + if err := triggerUpgradeRolloutSequence(d, meta, "NODE", minNodeVer); err != nil { + return err + } + } + } + return nil +}(); err != nil { + resourceGKEHub2RolloutSequencePostCreateFailure(d, meta) + return err +} diff --git a/mmv1/templates/terraform/post_update/gkehub2_rollout_sequence.go.tmpl b/mmv1/templates/terraform/post_update/gkehub2_rollout_sequence.go.tmpl new file mode 100644 index 000000000000..dda5554828aa --- /dev/null +++ b/mmv1/templates/terraform/post_update/gkehub2_rollout_sequence.go.tmpl @@ -0,0 +1,49 @@ +minCPChanged := d.HasChange("min_control_plane_version") +minNodeChanged := d.HasChange("min_node_version") + +if !minCPChanged && !minNodeChanged { + return nil +} + +log.Printf("[DEBUG] Polling RolloutSequence initialization.") +if err := pollSequenceInitialization(d, meta); err != nil { + return fmt.Errorf("failed to wait for RolloutSequence to initialize: %w", err) +} + +log.Printf("[DEBUG] Fetching current RolloutSequence target versions.") +targetCPVer, targetNodeVer, err := fetchCurrentTargetVersions(d, meta) +if err != nil { + return err +} + +if minCPChanged { + minCPVer := d.Get("min_control_plane_version").(string) + shouldUpgrade, err := shouldUpgradeRolloutSequence(minCPVer, targetCPVer) + if err != nil { + return err + } + if shouldUpgrade { + log.Printf("[DEBUG] min_control_plane_version (%s) > target_control_plane_version (%s). Upgrading RolloutSequence.", minCPVer, targetCPVer) + if err := triggerUpgradeRolloutSequence(d, meta, "CONTROL_PLANE", minCPVer); err != nil { + return err + } + } else { + log.Printf("[DEBUG] min_control_plane_version (%s) <= target_control_plane_version (%s). Skipping RolloutSequence upgrade.", minCPVer, targetCPVer) + } +} + +if minNodeChanged { + minNodeVer := d.Get("min_node_version").(string) + shouldUpgrade, err := shouldUpgradeRolloutSequence(minNodeVer, targetNodeVer) + if err != nil { + return err + } + if shouldUpgrade { + log.Printf("[DEBUG] min_node_version (%s) > target_node_version (%s). Upgrading RolloutSequence.", minNodeVer, targetNodeVer) + if err := triggerUpgradeRolloutSequence(d, meta, "NODE", minNodeVer); err != nil { + return err + } + } else { + log.Printf("[DEBUG] min_node_version (%s) <= target_node_version (%s). Skipping RolloutSequence upgrade.", minNodeVer, targetNodeVer) + } +} diff --git a/mmv1/templates/terraform/samples/services/gkehub2/gke_hub_rollout_sequence_basic.tf.tmpl b/mmv1/templates/terraform/samples/services/gkehub2/gke_hub_rollout_sequence_basic.tf.tmpl new file mode 100644 index 000000000000..3f902f7745b6 --- /dev/null +++ b/mmv1/templates/terraform/samples/services/gkehub2/gke_hub_rollout_sequence_basic.tf.tmpl @@ -0,0 +1,20 @@ +resource "google_gke_hub_rollout_sequence" "rollout_sequence" { + provider = google-beta + rollout_sequence_id = "tf-test-rs-basic-%{rollout_sequence_id}-%{random_suffix}" + display_name = "Basic Rollout Sequence" + ignored_clusters_selector { + label_selector = "resource.labels.ignored == 'true'" + } + stages { + fleet_projects = ["projects/%{project_id}"] + soak_duration = "30s" + } + auto_upgrade_config { + rollout_creation_scope { + upgrade_types = [ + "CONTROL_PLANE_PATCH", + "NODE_PATCH" + ] + } + } +} diff --git a/mmv1/templates/terraform/samples/services/gkehub2/gke_hub_rollout_sequence_update.tf.tmpl b/mmv1/templates/terraform/samples/services/gkehub2/gke_hub_rollout_sequence_update.tf.tmpl new file mode 100644 index 000000000000..f412e45e35b8 --- /dev/null +++ b/mmv1/templates/terraform/samples/services/gkehub2/gke_hub_rollout_sequence_update.tf.tmpl @@ -0,0 +1,30 @@ +resource "google_gke_hub_rollout_sequence" "rollout_sequence" { + provider = google-beta + rollout_sequence_id = "tf-test-rs-basic-%{rollout_sequence_id}-%{random_suffix}" + display_name = "Modified Rollout Sequence" + ignored_clusters_selector { + label_selector = "resource.labels.ignored == 'super_true'" + } + stages { + fleet_projects = ["projects/%{project_id}"] + cluster_selector { + label_selector = "resource.labels.canary=='true'" + } + soak_duration = "30s" + } + stages { + fleet_projects = ["projects/%{project_id}"] + soak_duration = "60s" + } + auto_upgrade_config { + rollout_creation_scope { + upgrade_types = [ + "CONTROL_PLANE_PATCH", + "NODE_PATCH" + ] + } + } + labels = { + some_key = "some_value" + } +} diff --git a/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_rollout_sequence_test.go.tmpl b/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_rollout_sequence_test.go.tmpl index a070e3895a9a..f3647c246fd6 100644 --- a/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_rollout_sequence_test.go.tmpl +++ b/mmv1/third_party/terraform/services/gkehub2/resource_gke_hub_rollout_sequence_test.go.tmpl @@ -1,139 +1,133 @@ -package gkehub2_test +package gkehub2 + {{ if ne $.TargetVersionName "ga" }} + import ( - "fmt" - "log" - "strconv" - "strings" "testing" - "time" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/plancheck" - "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-provider-google/google/acctest" - _ "github.com/hashicorp/terraform-provider-google/google/services/gkehub2" - "github.com/hashicorp/terraform-provider-google/google/envvar" - "github.com/hashicorp/terraform-provider-google/google/tpgresource" - transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" - - "google.golang.org/api/googleapi" ) -var ( - _ = fmt.Sprintf - _ = log.Print - _ = strconv.Atoi - _ = strings.Trim - _ = time.Now - _ = resource.TestMain - _ = terraform.NewState - _ = envvar.TestEnvVar - _ = tpgresource.SetLabels - _ = transport_tpg.Config{} - _ = googleapi.Error{} -) - -// Since there can only be a single rollout sequence in a given host project, -// create and update tests need to run sequentially, as a single test function. -func TestAccGKEHub2RolloutSequence(t *testing.T) { +func TestShouldUpgradeRolloutSequence(t *testing.T) { t.Parallel() - context := map[string]interface{}{ - "project_id": envvar.GetTestProjectFromEnv(), - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), - Steps: []resource.TestStep{ - { - Config: testAccGKEHub2RolloutSequence_basic(context), - }, - { - ResourceName: "google_gke_hub_rollout_sequence.rollout_sequence", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"labels", "rollout_sequence_id", "terraform_labels"}, - }, - { - Config: testAccGKEHub2RolloutSequence_update(context), - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectResourceAction("google_gke_hub_rollout_sequence.rollout_sequence", plancheck.ResourceActionUpdate), - }, - }, - }, - { - ResourceName: "google_gke_hub_rollout_sequence.rollout_sequence", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"labels", "rollout_sequence_id", "terraform_labels"}, - }, + tests := []struct { + name string + minCPVer string + targetVer string + want bool + wantErr bool + }{ + { + name: "minor version bump, want upgrade", + minCPVer: "1.28.0-gke.0", + targetVer: "1.27.10-gke.1200", + want: true, + wantErr: false, }, - }) -} + { + name: "patch version bump, want upgrade", + minCPVer: "1.27.5-gke.0", + targetVer: "1.27.3-gke.100", + want: true, + wantErr: false, + }, + { + name: "GKE patch version bump, want upgrade", + minCPVer: "1.27.3-gke.200", + targetVer: "1.27.3-gke.100", + want: true, + wantErr: false, + }, + { + name: "patch version > GKE patch version, want upgrade", + minCPVer: "1.27.1", + targetVer: "1.27.1-gke.100", + want: true, + wantErr: false, + }, + { + name: "missing patch version implicitly padded with zero, want NO upgrade", + minCPVer: "1.27", + targetVer: "1.27.1", + want: false, + wantErr: false, + }, + { + name: "minor version drop, want NO upgrade", + minCPVer: "1.26.5-gke.100", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: false, + }, + { + name: "patch version drop, want NO upgrade", + minCPVer: "1.27.2-gke.500", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: false, + }, + { + name: "GKE patch version drop, want NO upgrade", + minCPVer: "1.27.3-gke.50", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: false, + }, + { + name: "no version change, want NO upgrade", + minCPVer: "1.27.3-gke.100", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: false, + }, + { + name: "empty target version, want upgrade", + minCPVer: "1.27.3-gke.100", + targetVer: "", + want: true, + wantErr: false, + }, + { + name: "empty min version, want NO upgrade", + minCPVer: "", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: false, + }, + { + name: "malformed min version syntax, want error", + minCPVer: "invalid-semver", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: true, + }, + { + name: "malformed target version syntax, want error", + minCPVer: "1.27.3-gke.100", + targetVer: "invalid-gke-rev", + want: false, + wantErr: true, + }, + { + name: "latest alias not supported, want error", + minCPVer: "latest", + targetVer: "1.27.3-gke.100", + want: false, + wantErr: true, + }, + } -func testAccGKEHub2RolloutSequence_basic(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_gke_hub_rollout_sequence" "rollout_sequence" { - provider = google-beta - rollout_sequence_id = "tf-test-rs-basic-%{random_suffix}" - display_name = "Basic Rollout Sequence" - ignored_clusters_selector { - label_selector = "resource.labels.ignored == 'true'" - } - stages { - fleet_projects = ["projects/%{project_id}"] - soak_duration = "60s" - } - auto_upgrade_config { - rollout_creation_scope { - upgrade_types = [ - "CONTROL_PLANE_MINOR", - "CONTROL_PLANE_PATCH", - "NODE_MINOR", - "NODE_PATCH" - ] - } - } -} -`, context) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := shouldUpgradeRolloutSequence(tt.minCPVer, tt.targetVer) + if (err != nil) != tt.wantErr { + t.Errorf("shouldUpgradeRolloutSequence(%q, %q) error = %v, wantErr %v", tt.minCPVer, tt.targetVer, err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("shouldUpgradeRolloutSequence(%q, %q) = %v, want %v", tt.minCPVer, tt.targetVer, got, tt.want) + } + }) + } } -func testAccGKEHub2RolloutSequence_update(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_gke_hub_rollout_sequence" "rollout_sequence" { - provider = google-beta - rollout_sequence_id = "tf-test-rs-basic-%{random_suffix}" - display_name = "Modified Rollout Sequence" - ignored_clusters_selector { - label_selector = "resource.labels.ignored == 'super_true'" - } - stages { - fleet_projects = ["projects/%{project_id}"] - cluster_selector { - label_selector = "resource.labels.canary=='true'" - } - soak_duration = "30s" - } - stages { - fleet_projects = ["projects/%{project_id}"] - soak_duration = "60s" - } - auto_upgrade_config { - rollout_creation_scope { - upgrade_types = [ - "CONTROL_PLANE_PATCH", - "NODE_PATCH" - ] - } - } - labels = { - some_key = "some_value" - } -} -`, context) -} {{ end }} \ No newline at end of file