From gcp-hcp
Use when adding a new GCP service account for Workload Identity Federation (WIF) in HyperShift and related GCP HCP repositories. Covers API types, IAM bindings, CRD regeneration, CLS backend/controller propagation, and CLI updates across hypershift, gcp-hcp-infra, cls-backend, cls-controller, and gcp-hcp-cli.
npx claudepluginhub openshift-online/gcp-hcp --plugin gcp-hcpThis skill uses the workspace's default tool permissions.
This skill documents the steps required to add a new GCP service account for Workload Identity Federation (WIF) in HyperShift and related projects.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Analyzes competition with Porter's Five Forces, Blue Ocean Strategy, and positioning maps to identify differentiation opportunities and market positioning for startups and pitches.
This skill documents the steps required to add a new GCP service account for Workload Identity Federation (WIF) in HyperShift and related projects.
When adding a new GCP service account (e.g., for a new controller like Cloud Controller Manager), changes are required across multiple repositories to ensure the service account is:
hypershift create iam gcp commandFile: api/hypershift/v1beta1/gcp.go
Add the new field to GCPServiceAccountsEmails struct:
type GCPServiceAccountsEmails struct {
// Existing fields...
NodePool string `json:"nodePool,omitempty"`
ControlPlane string `json:"controlPlane,omitempty"`
// NEW: Add your service account field
CloudController string `json:"cloudController,omitempty"`
}
After editing, run:
make api
File: cmd/cluster/gcp/create.go
Test: cmd/cluster/gcp/create_test.go
Add a CLI flag so users can pass the new service account email when creating a cluster via hypershift create cluster gcp. Without this, the +required API field cannot be set through the CLI, producing invalid HostedCluster resources.
Follow the pattern of the existing --cloud-controller-service-account flag:
const flagCloudControllerServiceAccount = "cloud-controller-service-account"
// NEW:
const flag<ServiceAccount>ServiceAccount = "<service-account>-service-account"
RawCreateOptions and ValidatedCreateOptions:type RawCreateOptions struct {
// ...
<ServiceAccount>ServiceAccount string
}
cmd.Flags().StringVar(&opts.<ServiceAccount>ServiceAccount, flag<ServiceAccount>ServiceAccount, "", "GCP service account email for <Component>")
ValidateCreateOptions():if len(opts.<ServiceAccount>ServiceAccount) == 0 {
return nil, fmt.Errorf("--%s is required", flag<ServiceAccount>ServiceAccount)
}
CompleteCreateOptions():hostedCluster.Spec.Platform.GCP.WorkloadIdentity.ServiceAccountsEmails.<ServiceAccount> = opts.<ServiceAccount>ServiceAccount
cmd/cluster/gcp/create_test.go:
File: hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp.go
Test: hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go
Add credential secret creation and validation for the new service account. Follow the pattern of CloudControllerCredsSecret:
func <ServiceAccount>CredsSecret(controlPlaneNamespace string) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: controlPlaneNamespace,
Name: "<service-account>-creds",
},
}
}
credentialSecrets map in ReconcileCredentials():credentialSecrets := map[string]*corev1.Secret{
// existing entries...
hcluster.Spec.Platform.GCP.WorkloadIdentity.ServiceAccountsEmails.<ServiceAccount>: <ServiceAccount>CredsSecret(controlPlaneNamespace),
}
validateWorkloadIdentityConfiguration():if wif.ServiceAccountsEmails.<ServiceAccount> == "" {
return fmt.Errorf("<service account> service account email is required")
}
test<ServiceAccount>GSAvalidHostedCluster() helperGCPServiceAccountsEmailsFiles:
test/e2e/create_cluster_test.gotest/e2e/v2/tests/api_ux_validation_test.goServiceAccountsEmails fixture in test/e2e/create_cluster_test.go:ServiceAccountsEmails: hyperv1.GCPServiceAccountsEmails{
NodePool: "nodepool@my-project-123.iam.gserviceaccount.com",
ControlPlane: "controlplane@my-project-123.iam.gserviceaccount.com",
CloudController: "cloudcontroller@my-project-123.iam.gserviceaccount.com",
Storage: "storage@my-project-123.iam.gserviceaccount.com",
<ServiceAccount>: "<serviceaccount>@my-project-123.iam.gserviceaccount.com", // NEW
}
test/e2e/v2/tests/api_ux_validation_test.go to the DescribeTable block where other GSA fields are validated:Entry("it should reject invalid <ServiceAccount> service account email",
func(spec *hyperv1.GCPPlatformSpec) {
spec.WorkloadIdentity.ServiceAccountsEmails.<ServiceAccount> = "invalid-<service-account>-email"
},
"<serviceAccount> in body"),
This follows the pattern used by CloudController, Storage, ControlPlane, and NodePool.
File: cmd/infra/gcp/iam-bindings.json
Add the new service account definition with required IAM roles:
{
"name": "cloud-controller",
"displayName": "Cloud Controller Manager Service Account",
"description": "Service account for GCP Cloud Controller Manager",
"roles": [
"roles/compute.loadBalancerAdmin",
"roles/compute.securityAdmin",
"roles/compute.viewer"
],
"k8sServiceAccount": {
"namespace": "kube-system",
"name": "cloud-controller-manager"
}
}
Key fields:
name: Short identifier used in WIF bindings (becomes {infraId}-{name})roles: GCP IAM roles required by the componentk8sServiceAccount: K8s SA that will impersonate this GCP SA via WIFNote: The create_iam.go and destroy_iam.go use //go:embed to load this file, so no code changes needed there.
After Steps 1 and 2 are merged to HyperShift main, regenerate hypershift.yaml
in the infra repo so the management cluster CRDs reflect the new field.
File generated: kustomize/hypershift/hypershift.yaml
cd /path/to/gcp-hcp-infra/kustomize/hypershift
./update.sh
This script:
podmanhypershift install render with GCP-specific flagshypershift.yaml with the updated manifests + CRDsThen commit and push the updated hypershift.yaml:
git add kustomize/hypershift/hypershift.yaml
git commit -m "chore: regenerate hypershift.yaml with <field> API addition"
git push
Do not skip this step. If
hypershift.yamlis not updated, the management cluster will run with stale CRDs that don't include the new SA field, causing HostedCluster validation to reject the new field silently.
File: cls-backend/internal/models/cluster.go
Add the field to WIFServiceAccountsRef struct:
type WIFServiceAccountsRef struct {
NodePoolEmail string `json:"nodePoolEmail"`
ControlPlaneEmail string `json:"controlPlaneEmail"`
CloudControllerEmail string `json:"cloudControllerEmail"` // NEW
}
File: cls-controller/deployments/helm-cls-hypershift-client/templates/controllerconfig.yaml
Add the field to the HostedCluster template's serviceAccountsEmails section:
workloadIdentity:
projectNumber: "{{`{{ .cluster.spec.platform.gcp.workloadIdentity.projectNumber }}`}}"
poolID: {{`{{ .cluster.spec.platform.gcp.workloadIdentity.poolID }}`}}
providerID: {{`{{ .cluster.spec.platform.gcp.workloadIdentity.providerID }}`}}
serviceAccountsEmails:
nodePool: {{`{{ .cluster.spec.platform.gcp.workloadIdentity.serviceAccountsRef.nodePoolEmail }}`}}
controlPlane: {{`{{ .cluster.spec.platform.gcp.workloadIdentity.serviceAccountsRef.controlPlaneEmail }}`}}
cloudController: {{`{{ .cluster.spec.platform.gcp.workloadIdentity.serviceAccountsRef.cloudControllerEmail }}`}} # NEW
File: gcp-hcp-cli/src/gcphcp/utils/hypershift.py
SERVICE_ACCOUNTS = {
"ctrlplane-op": "Control Plane Operator",
"nodepool-mgmt": "Node Pool Management",
"cloud-controller": "Cloud Controller Manager", # NEW
}
def iam_config_to_wif_spec(iam_config: Dict[str, Any]) -> Dict[str, Any]:
# ...
return {
"projectNumber": iam_config.get("projectNumber"),
"poolID": pool.get("poolId"),
"providerID": pool.get("providerId"),
"serviceAccountsRef": {
"controlPlaneEmail": service_accounts.get("ctrlplane-op"),
"nodePoolEmail": service_accounts.get("nodepool-mgmt"),
"cloudControllerEmail": service_accounts.get("cloud-controller"), # NEW
},
}
After making all changes:
make api runs successfully in hypershiftmake verify passes in hypershifthypershift create cluster gcp --help shows the new --<service-account>-service-account flagtest/e2e/create_cluster_test.go include the new fieldtest/e2e/v2/tests/api_ux_validation_test.go includes the new fieldkustomize/hypershift/update.sh runs successfully in gcp-hcp-infrahypershift.yaml in gcp-hcp-infra contains the new SA field in the CRD schemago build ./... works in cls-backendhypershift create iam gcp - should create the new service accounthypershift create cluster gcp with the new flag - should set the field in the HostedCluster specImportant: All code changes should be pushed to your fork of each repository. Then create a Pull Request from your fork to the upstream repository.
Example workflow:
- Fork the upstream repo (if not already done)
- Clone your fork locally
- Add upstream as a remote:
git remote add upstream <upstream-url>- Create a branch, make changes, push to your fork
- Open PR from your fork to upstream
Upstream: https://github.com/openshift/hypershift
Your fork: https://github.com/<your-username>/hypershift
cd /path/to/hypershift
# Create branch
git checkout -b add-<service-account>-sa
# Stage changes
git add api/hypershift/v1beta1/gcp.go
git add cmd/infra/gcp/iam-bindings.json
git add cmd/cluster/gcp/create.go
git add cmd/cluster/gcp/create_test.go
git add hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp.go
git add hypershift-operator/controllers/hostedcluster/internal/platform/gcp/gcp_test.go
git add test/e2e/create_cluster_test.go
git add test/e2e/v2/tests/api_ux_validation_test.go
git add client/ # Generated client code
git add vendor/ # Vendored API changes
# Commit
git commit -m "feat(gcp): add <ServiceAccount> service account for <Component>
Add <ServiceAccount> field to GCPServiceAccountsEmails and IAM bindings
for <Component> authentication via Workload Identity Federation.
IAM roles:
- roles/...
- roles/...
Co-Authored-By: Claude <noreply@anthropic.com>"
# Push and create PR
git push -u origin add-<service-account>-sa
gh pr create --title "feat(gcp): add <ServiceAccount> service account for <Component>" \
--body "## Summary
- Add \`<ServiceAccount>\` field to \`GCPServiceAccountsEmails\` struct
- Add IAM bindings for <Component> with required roles
- Add \`--<service-account>-service-account\` CLI flag to \`hypershift create cluster gcp\`
- Add credential secret and WIF validation in hypershift-operator
- Update generated client code and tests
## Test plan
- [ ] \`make verify\` passes
- [ ] \`hypershift create cluster gcp\` accepts the new flag
- [ ] \`hypershift create iam gcp\` creates the new service account
- [ ] New service account has correct IAM role bindings
Co-Authored-By: Claude <noreply@anthropic.com>"
cd /path/to/gcp-hcp-infra/kustomize/hypershift
# Regenerate from main (see Step 3)
./update.sh
cd /path/to/gcp-hcp-infra
# Create branch
git checkout -b add-<service-account>-sa
# Stage changes
git add kustomize/hypershift/hypershift.yaml
# Commit
git commit -m "chore: regenerate hypershift.yaml with <field> API addition"
# Push and create PR
git push -u origin add-<service-account>-sa
gh pr create --title "chore: regenerate hypershift.yaml with <field> API addition" \
--body "## Summary
Regenerate \`kustomize/hypershift/hypershift.yaml\` to pick up the updated
CRD schema from hypershift PR: <link>.
The new \`<field>\` SA field on \`GCPServiceAccountsEmails\` would be silently
rejected by the management cluster without this update.
## Dependencies
- Requires hypershift PR: <link>"
Upstream: https://github.com/apahim/cls-backend
Your fork: https://github.com/<your-username>/cls-backend
cd /path/to/cls-backend
# Create branch
git checkout -b add-<service-account>-sa
# Stage changes
git add internal/models/cluster.go
# Commit
git commit -m "feat: add <ServiceAccount>Email to WIFServiceAccountsRef
Support the new <ServiceAccount> service account for GCP clusters.
Co-Authored-By: Claude <noreply@anthropic.com>"
# Push and create PR
git push -u origin add-<service-account>-sa
gh pr create --title "feat: add <ServiceAccount>Email to WIFServiceAccountsRef" \
--body "## Summary
Add \`<ServiceAccount>Email\` field to \`WIFServiceAccountsRef\` struct to support
the new <Component> service account for GCP HyperShift clusters.
## Dependencies
- Requires hypershift PR: <link>
Co-Authored-By: Claude <noreply@anthropic.com>"
Upstream: https://github.com/apahim/cls-controller
Your fork: https://github.com/<your-username>/cls-controller
cd /path/to/cls-controller
# Create branch
git checkout -b add-<service-account>-sa
# Stage changes
git add deployments/helm-cls-hypershift-client/templates/controllerconfig.yaml
# Commit
git commit -m "feat: add <serviceAccount> to HostedCluster template
Include the new <ServiceAccount> service account in the HostedCluster
workloadIdentity configuration.
Co-Authored-By: Claude <noreply@anthropic.com>"
# Push and create PR
git push -u origin add-<service-account>-sa
gh pr create --title "feat: add <serviceAccount> to HostedCluster template" \
--body "## Summary
Add \`<serviceAccount>\` field to the HostedCluster template's
\`serviceAccountsEmails\` section.
## Dependencies
- Requires hypershift PR: <link>
- Requires cls-backend PR: <link>
Co-Authored-By: Claude <noreply@anthropic.com>"
Upstream: https://github.com/apahim/gcp-hcp-cli
Your fork: https://github.com/<your-username>/gcp-hcp-cli
cd /path/to/gcp-hcp-cli
# Create branch
git checkout -b add-<service-account>-sa
# Stage changes
git add src/gcphcp/utils/hypershift.py
# Commit
git commit -m "feat: add <service-account> to SERVICE_ACCOUNTS mapping
Support the new <ServiceAccount> service account from hypershift IAM output.
Co-Authored-By: Claude <noreply@anthropic.com>"
# Push and create PR
git push -u origin add-<service-account>-sa
gh pr create --title "feat: add <service-account> to SERVICE_ACCOUNTS mapping" \
--body "## Summary
- Add \`<service-account>\` to \`SERVICE_ACCOUNTS\` constant
- Add \`<serviceAccount>Email\` to \`iam_config_to_wif_spec\` output
## Dependencies
- Requires hypershift PR: <link>
Co-Authored-By: Claude <noreply@anthropic.com>"
PRs should be merged in this order to avoid breaking changes:
hypershift.yaml CRDsNote: Steps 3-5 can be merged in parallel if coordinated, but hypershift and gcp-hcp-infra must be updated first so the management cluster CRDs are in sync.
The CloudController service account was added with these IAM roles:
roles/compute.loadBalancerAdmin - Manage load balancersroles/compute.securityAdmin - Manage firewall rulesroles/compute.viewer - Read compute resourcesBound to K8s ServiceAccount: kube-system/cloud-controller-manager