kubernetes/kops · error

found ServiceAccount but email did not match expected; got %

Error message

found ServiceAccount but email did not match expected; got %q; want %q

What it means

Find in the GCE ServiceAccount task returns this when the IAM lookup succeeded but sa.Email differs from the email kOps expected. This guards against an FQN resolving to a different account than intended (stale spec, renamed/external accounts), and kOps refuses to proceed rather than diff against the wrong object.

Source

Thrown at upup/pkg/fi/cloudup/gcetasks/serviceaccount.go:77

		return e, nil
	}

	_, projectID, err := gce.SplitServiceAccountEmail(email)
	if err != nil {
		return nil, err
	}
	fqn := "projects/" + projectID + "/serviceAccounts/" + email
	sa, err := cloud.IAM().ServiceAccounts().Get(ctx, fqn)
	if err != nil {
		if gce.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing ServiceAccount %q: %w", fqn, err)
	}

	// Check the email actually matches what we expect
	if email != sa.Email {
		return nil, fmt.Errorf("found ServiceAccount but email did not match expected; got %q; want %q", sa.Email, email)
	}

	actual := &ServiceAccount{}
	actual.DisplayName = &sa.DisplayName
	actual.Description = &sa.Description
	actual.Email = &sa.Email

	// Prevent spurious changes
	actual.Lifecycle = e.Lifecycle
	actual.Name = e.Name
	actual.Shared = e.Shared

	return actual, nil
}

func (e *ServiceAccount) Run(c *fi.CloudupContext) error {
	return fi.CloudupDefaultDeltaRunMethod(e, c)
}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Compare 'got' vs 'want' in the message and correct the service account email in the cluster spec to the canonical value
  2. Ensure the project ID used to build the FQN is the project that actually owns the service account (shared VPC: use the service project's account)
  3. Use Google's canonical email form (lowercase, correct legacy project-id prefix) — run `gcloud iam service-accounts describe <email>` to see the true email
  4. If the account is wrong/missing, recreate it (kops will manage one) or update cluster spec references

Example fix

// before (wrong email in spec)
serviceAccount: my-app@wrong-project.iam.gserviceaccount.com
// after (canonical email from gcloud iam service-accounts describe)
serviceAccount: my-app@my-project.iam.gserviceaccount.com
Defensive patterns

Strategy: validation

Validate before calling

// Resolve the canonical email before the apply
sa, err := iamClient.Projects.ServiceAccounts.Get("projects/"+projectID+"/serviceAccounts/"+email).Do()
if err != nil { return err }
if sa.Email != email {
    fmt.Printf("use canonical email %s instead of %s\n", sa.Email, email)
}

Type guard

func emailMatches(sa *iam.ServiceAccount, want string) bool {
    return sa != nil && strings.EqualFold(sa.Email, want)
}

Try / catch

if email != sa.Email {
    return nil, fmt.Errorf("found ServiceAccount but email did not match expected; got %q; want %q", sa.Email, email)
}
// caller:
if err != nil && strings.Contains(err.Error(), "email did not match expected") {
    got := extractQuoted(err, 0)
    fmt.Printf("fix cluster spec to use canonical email %s\n", got)
}

Prevention

When it happens

Trigger: cloud.IAM().ServiceAccounts().Get(ctx, "projects/<projectID>/serviceAccounts/<email>") returns a service account whose sa.Email != the requested email — e.g. the spec uses an alias/custom email format, the project ID in the FQN points to a different project (Google can resolve cross-project FQNs), or domain-scoped/external accounts where the returned Email is normalized differently.

Common situations: Cluster spec referencing a service account from another project (shared VPC setups); emails containing different casing or prefix (e.g. legacy project IDs) than Google's canonical form; spec edited by hand with a wrong email; org policy/project migrations renaming accounts.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/be010882226ec101. Report an issue: GitHub.