{"record":{"id":"814974ad073eb84d","repo":"kubernetes/kops","slug":"cannot-apply-changes-to-serviceaccount-v","errorCode":null,"errorMessage":"cannot apply changes to ServiceAccount: %v","messagePattern":"cannot apply changes to ServiceAccount: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/cloudup/gcetasks/serviceaccount.go","lineNumber":160,"sourceCode":"\t\tif changes.Description != nil || changes.DisplayName != nil {\n\t\t\tsa := &iam.ServiceAccount{\n\t\t\t\tEmail:       email,\n\t\t\t\tDescription: fi.ValueOf(e.Description),\n\t\t\t\tDisplayName: fi.ValueOf(e.DisplayName),\n\t\t\t}\n\n\t\t\t_, err := cloud.IAM().ServiceAccounts().Update(ctx, fqn, sa)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating ServiceAccount %q: %w\", fqn, err)\n\t\t\t}\n\n\t\t\tchanges.Description = nil\n\t\t\tchanges.DisplayName = nil\n\t\t}\n\n\t\tempty := &ServiceAccount{}\n\t\tif !reflect.DeepEqual(empty, changes) {\n\t\t\treturn fmt.Errorf(\"cannot apply changes to ServiceAccount: %v\", changes)\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype terraformServiceAccount struct {\n\tAccountID   *string `cty:\"account_id\"`\n\tProjectID   *string `cty:\"project\"`\n\tDescription *string `cty:\"description\"`\n\tDisplayName *string `cty:\"display_name\"`\n}\n\nfunc (_ *ServiceAccount) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *ServiceAccount) error {\n\tshared := fi.ValueOf(e.Shared)\n\tif shared {\n\t\t// Not terraform owned / managed\n\t\treturn nil","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/cloudup/gcetasks/serviceaccount.go#L142-L178","documentation":"After handling Description/DisplayName changes, RenderGCE nils them out and checks that no other fields remain in the changes struct (via reflect.DeepEqual against an empty ServiceAccount). If leftover changes remain, kOps has no API to apply them to an existing service account (GCP service accounts are nearly immutable), so it fails rather than silently ignoring drift.","triggerScenarios":"Applying a non-shared ServiceAccount task where `a != nil` and the computed `changes` still contains non-zero fields after Description/DisplayName are cleared — i.e. changes to immutable fields like Email, Name, Shared, or Lifecycle.","commonSituations":"Renaming the service account email in the cluster spec while reusing the same task name (email is the identity, it cannot be updated in place); changing Shared from false to true (or vice versa) without recreating; a Find-phase bug failing to copy back a field causing spurious diffs.","solutions":["Identify the leftover changed field in the printed `changes` value; if it is Email, treat it as create-new/delete-old rather than an in-place rename.","Create a new task (or new cluster add-on name) with the new email and remove the old service account explicitly.","If Shared changed, revert the spec change or delete and recreate the task so Find/apply agree on lifecycle ownership.","If the diff is spurious (fields kOps should have ignored), report/fix the Find method to normalize those fields (compare with serviceaccount.go:80-88)."],"exampleFix":"// before: trying to rename in place\nEmail: fi.String(\"new-robot@example-project.iam.gserviceaccount.com\") // same task name\n// after: new task / new email identity, delete old one separately\nName: fi.String(\"newrobot-sa\"), Email: fi.String(\"new-robot@example-project.iam.gserviceaccount.com\")","handlingStrategy":"validation","validationCode":"// Reject in-place email \"rename\" before applying\nif existing != nil && existing.Email != nil && e.Email != nil && *existing.Email != *e.Email {\n    return fmt.Errorf(\"service account email is immutable; create a new task for %q and delete %q\", *e.Email, *existing.Email)\n}","typeGuard":"func onlyMutableFieldsChanged(changes *ServiceAccount) bool {\n    return changes.Email == nil && changes.Shared == nil && changes.Name == nil\n}","tryCatchPattern":"if err := kopsUpdate(); err != nil {\n    if strings.Contains(err.Error(), \"cannot apply changes to ServiceAccount\") {\n        log.Print(\"unsupported field change; print changes and split into create-new/delete-old\")\n    }\n    return err\n}","preventionTips":["Never rename a service account email in place — create a new task and delete the old account.","Don't toggle Shared on an existing task; recreate the task instead.","Review `kops update cluster --dry-run` diffs before applying.","If you see a spurious diff, compare against Find's normalization of Name/Shared/Lifecycle."],"tags":["gce","gcp","iam","service-account","immutable-field"],"backgroundTag":"immutable-field-update-rejected","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}