Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions mmv1/products/gkehub2/RolloutSequence.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
199 changes: 199 additions & 0 deletions mmv1/templates/terraform/constants/gkehub2_rollout_sequence.go.tmpl
Original file line number Diff line number Diff line change
@@ -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 }}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading
Loading