Skip to content
Open
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
1 change: 1 addition & 0 deletions Containerfile.control-plane
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ LABEL io.openshift.hypershift.restricted-psa=true
LABEL io.openshift.hypershift.control-plane-pki-operator-signs-csrs=true
LABEL io.openshift.hypershift.hosted-cluster-config-operator-reports-node-count=true
LABEL io.openshift.hypershift.control-plane-operator-supports-kas-custom-kubeconfig=true
LABEL io.openshift.hypershift.control-plane-operator-supports-gcp-resource-tags=true
LABEL io.openshift.hypershift.control-plane-operator.v2-isdefault=true
1 change: 1 addition & 0 deletions Dockerfile.control-plane
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ LABEL io.openshift.hypershift.restricted-psa=true
LABEL io.openshift.hypershift.control-plane-pki-operator-signs-csrs=true
LABEL io.openshift.hypershift.hosted-cluster-config-operator-reports-node-count=true
LABEL io.openshift.hypershift.control-plane-operator-supports-kas-custom-kubeconfig=true
LABEL io.openshift.hypershift.control-plane-operator-supports-gcp-resource-tags=true
Comment thread
gbarabasz marked this conversation as resolved.
LABEL io.openshift.hypershift.control-plane-operator.v2-isdefault=true
1 change: 1 addition & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ LABEL io.openshift.hypershift.restricted-psa=true
LABEL io.openshift.hypershift.control-plane-pki-operator-signs-csrs=true
LABEL io.openshift.hypershift.hosted-cluster-config-operator-reports-node-count=true
LABEL io.openshift.hypershift.control-plane-operator-supports-kas-custom-kubeconfig=true
LABEL io.openshift.hypershift.control-plane-operator-supports-gcp-resource-tags=true
LABEL io.openshift.hypershift.control-plane-operator.v2-isdefault=true
86 changes: 86 additions & 0 deletions api/hypershift/v1beta1/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,50 @@ type GCPResourceLabel struct {
Value *string `json:"value,omitempty"`
}

// GCPResourceTag selects a pre-existing Google Cloud Resource Manager tag by
// its parent, short key, and short value. The tag must be defined in the
// HostedCluster project or an organization before the cluster is created.
// See https://cloud.google.com/resource-manager/docs/tags/tags-overview.
type GCPResourceTag struct {
// parentID is the ID of the project or organization that defines the TagKey.
// When omitted, the HostedCluster's GCP project is used.
// An explicit value must be the HostedCluster's GCP project ID or a numeric
// organization ID. A numeric value is treated as an organization ID; project
// numbers are not supported.
// An organization ID consists of 1 to 32 decimal digits and must not start with 0.
// A project ID is 6 to 30 characters long, contains only lowercase letters, digits
// and hyphens, starts with a letter and must not end with a hyphen.
//
// +optional
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=32
// +kubebuilder:validation:XValidation:rule="self.matches('(^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$)')",message="parentID must be a numeric organization ID or a valid GCP project ID"
ParentID string `json:"parentID,omitempty"`

// key is required and is the short name of the pre-existing Resource Manager TagKey.
// A key is 1-63 characters, begins and ends with
// an ASCII alphanumeric character, and may contain letters, digits, '.',
// '_', or '-' between them.
Comment thread
everettraven marked this conversation as resolved.
//
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$')",message="key must start and end with an ASCII letter or digit and contain only ASCII letters, digits, '.', '_' or '-'"
Key string `json:"key,omitempty"`
Comment thread
everettraven marked this conversation as resolved.

// value is required and is the short name of the pre-existing Resource Manager TagValue for
// key. Exactly one value for a TagKey can be attached to a resource. A value
// is 1-63 characters, begins and ends with an ASCII alphanumeric character,
// and may contain ASCII letters and digits, `_-.@%=+:,*#&()[]{}`, and
// spaces between them.
Comment thread
everettraven marked this conversation as resolved.
//
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\\\\[\\\\]{}\\\\- ]{0,61}[a-zA-Z0-9])?$')",message="value must start and end with an ASCII letter or digit and contain only ASCII letters, digits, spaces or the characters _-.@%=+:,*#&()[]{}"
Value string `json:"value,omitempty"`
}

// GCPEndpointAccessType defines the endpoint access type for GCP clusters.
// Equivalent to AWS EndpointAccessType but adapted for GCP networking model.
type GCPEndpointAccessType string
Expand Down Expand Up @@ -107,6 +151,8 @@ type GCPNetworkConfig struct {
// +kubebuilder:validation:XValidation:rule="self.workloadIdentity.serviceAccountsEmails.storage.contains('@') && self.workloadIdentity.serviceAccountsEmails.storage.endsWith('@' + self.project + '.iam.gserviceaccount.com')",message="storage service account must belong to the same project"
// +kubebuilder:validation:XValidation:rule="self.workloadIdentity.serviceAccountsEmails.imageRegistry.contains('@') && self.workloadIdentity.serviceAccountsEmails.imageRegistry.endsWith('@' + self.project + '.iam.gserviceaccount.com')",message="imageRegistry service account must belong to the same project"
// +kubebuilder:validation:XValidation:rule="self.workloadIdentity.serviceAccountsEmails.network.endsWith('@' + self.project + '.iam.gserviceaccount.com')",message="network service account must belong to the same project"
// +kubebuilder:validation:XValidation:rule="has(self.resourceTags) == has(oldSelf.resourceTags)",message="resourceTags may only be configured during installation",fieldPath=".resourceTags"
// +kubebuilder:validation:XValidation:rule="!has(self.resourceTags) || self.resourceTags.all(t, !has(t.parentID) || t.parentID.matches('^[1-9][0-9]{0,31}$') || t.parentID == self.project)",message="resourceTags parentID must be an organization ID or the GCP platform project",fieldPath=".resourceTags"
type GCPPlatformSpec struct {
// project is the GCP project ID.
// A valid project ID must satisfy the following rules:
Expand Down Expand Up @@ -161,6 +207,45 @@ type GCPPlatformSpec struct {
// +kubebuilder:validation:MaxItems=60
ResourceLabels []GCPResourceLabel `json:"resourceLabels,omitempty"`

// resourceTags are pre-existing Google Cloud Resource Manager tags to apply to
// GCP resources created for the hosted cluster.
// Currently, tags are applied to newly provisioned persistent disks and, when the
// image registry is enabled, to its storage bucket. Other GCP resources, such as
// worker VMs and their boot disks, are not tagged.
// The set of tagged resources is subject to change over time.
//
// When omitted, no tags are applied through this setting. When specified,
// the list must contain 1 to 50 entries, and each short key must be unique
// across the list, even when the tags have different parents. A tag's
// parentID defaults to the HostedCluster's GCP project if omitted.
// Google Cloud permits at most 50 directly attached tags per resource,
// including tags attached by other actors. Coordinate with external tagging
// automation to leave sufficient capacity; admission does not reserve tag slots.
//
// The referenced tags must already exist, and the storage and imageRegistry
// service accounts must have permission to use them. Neither is checked when the
// HostedCluster is created. When a tag cannot be applied, provisioning of the
// affected disk or bucket can fail, and the error is reported in the persistent
// volume claim's events or the image registry operator's status.
//
// resourceTags can only be set when the HostedCluster is created. It cannot be
// added, removed or changed afterwards. If the configured list must change,
// the HostedCluster must be recreated.
// Resource tags require a control plane operator image that supports GCP resource
// tag propagation. When that capability is unavailable, reconciliation fails
// with a message identifying resourceTags on the ReconciliationSucceeded
// condition, and dependent component deployments are blocked until a
// supporting control plane operator image is configured.
//
// +optional
Comment thread
gbarabasz marked this conversation as resolved.
// +listType=atomic
Comment thread
everettraven marked this conversation as resolved.
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=50
// +immutable
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resourceTags are immutable"
Comment thread
everettraven marked this conversation as resolved.
// +kubebuilder:validation:XValidation:rule="self.all(t, self.exists_one(u, u.key == t.key))",message="resourceTags keys must be unique across all parents"
ResourceTags []GCPResourceTag `json:"resourceTags,omitempty"`
Comment thread
gbarabasz marked this conversation as resolved.

// workloadIdentity configures Workload Identity Federation for the cluster.
// This enables secure, short-lived token-based authentication without storing
// long-term service account keys. These fields are immutable after cluster creation
Expand Down Expand Up @@ -349,6 +434,7 @@ type GCPServiceAccountsEmails struct {
// that manages GCS storage for the internal container image registry.
// This GSA requires the following IAM roles:
// - roles/storage.admin (Storage Admin - for creating and managing GCS buckets and objects)
// - roles/resourcemanager.tagUser (Tag User - for applying resource tags to the bucket)
// See cmd/infra/gcp/iam-bindings.json for the authoritative role definitions.
// Format: service-account-name@project-id.iam.gserviceaccount.com
//
Expand Down
2 changes: 2 additions & 0 deletions api/hypershift/v1beta1/hostedcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,8 @@ func PlatformTypes() []PlatformType {

// PlatformSpec specifies the underlying infrastructure provider for the cluster
// and is used to configure platform specific behavior.
//
// +openshift:validation:FeatureGateAwareXValidation:featureGate=GCPPlatform,rule="(has(self.gcp) && has(self.gcp.resourceTags)) == (has(oldSelf.gcp) && has(oldSelf.gcp.resourceTags))",message="resourceTags may only be configured during installation"
type PlatformSpec struct {
// type is the type of infrastructure provider for the cluster.
//
Expand Down
20 changes: 20 additions & 0 deletions api/hypershift/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading