diff --git a/api/hypershift/v1beta1/etcdbackup_types.go b/api/hypershift/v1beta1/etcdbackup_types.go new file mode 100644 index 000000000000..915400537359 --- /dev/null +++ b/api/hypershift/v1beta1/etcdbackup_types.go @@ -0,0 +1,366 @@ +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + SchemeBuilder.Register(func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &HCPEtcdBackup{}, + &HCPEtcdBackupList{}, + ) + return nil + }) +} + +// Condition types and reasons for HCPEtcdBackup. +const ( + // BackupCompleted indicates whether the etcd backup has completed. + BackupCompleted ConditionType = "BackupCompleted" + + BackupSucceededReason string = "BackupSucceeded" + BackupFailedReason string = "BackupFailed" + BackupAlreadyInProgressReason string = "BackupAlreadyInProgress" + EtcdUnhealthyReason string = "EtcdUnhealthy" +) + +// HCPEtcdBackupStorageType is the type of storage for etcd backups. +// +kubebuilder:validation:Enum=S3;AzureBlob +type HCPEtcdBackupStorageType string + +const ( + // S3BackupStorage indicates that the backup is stored in AWS S3. + S3BackupStorage HCPEtcdBackupStorageType = "S3" + + // AzureBlobBackupStorage indicates that the backup is stored in Azure Blob Storage. + AzureBlobBackupStorage HCPEtcdBackupStorageType = "AzureBlob" +) + +// SecretReference contains a reference to a Secret by name. +// The Secret must exist in the same namespace as the referencing resource. +type SecretReference struct { + // name is the name of the Secret. It must be a valid DNS-1123 subdomain: at most + // 253 characters, consisting of lowercase alphanumeric characters, hyphens, and periods. + // Each period-separated segment must start and end with an alphanumeric character. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$')",message="name must consist only of lowercase alphanumeric characters, hyphens, and periods. Each period-separated segment must start and end with an alphanumeric character." + Name string `json:"name,omitempty"` +} + +// HCPEtcdBackupSpec defines the desired state of HCPEtcdBackup. +// HCPEtcdBackup is a one-shot backup request; the entire spec is immutable once created. +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="HCPEtcdBackupSpec is immutable" +type HCPEtcdBackupSpec struct { + // storage defines the cloud storage backend where the etcd snapshot will be uploaded. + // +required + Storage HCPEtcdBackupStorage `json:"storage,omitzero"` +} + +// HCPEtcdBackupStorage defines the cloud storage backend configuration for the backup. +// Exactly one storage backend must be specified, matching the storageType discriminator. +// +union +// +kubebuilder:validation:XValidation:rule="self.storageType == 'S3' ? has(self.s3) : !has(self.s3)",message="s3 configuration is required when storageType is S3, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="self.storageType == 'AzureBlob' ? has(self.azureBlob) : !has(self.azureBlob)",message="azureBlob configuration is required when storageType is AzureBlob, and forbidden otherwise" +type HCPEtcdBackupStorage struct { + // storageType specifies the type of cloud storage backend for the etcd backup. + // Valid values are "S3" for AWS S3 storage and "AzureBlob" for Azure Blob Storage. + // +unionDiscriminator + // +required + StorageType HCPEtcdBackupStorageType `json:"storageType,omitempty"` + + // s3 specifies the S3 storage configuration for the etcd backup. + // Required when storageType is "S3", and forbidden otherwise. + // +optional + // +unionMember + S3 HCPEtcdBackupS3 `json:"s3,omitzero"` + + // azureBlob specifies the Azure Blob storage configuration for the etcd backup. + // Required when storageType is "AzureBlob", and forbidden otherwise. + // +optional + // +unionMember + AzureBlob HCPEtcdBackupAzureBlob `json:"azureBlob,omitzero"` +} + +// HCPEtcdBackupS3 defines the S3 storage configuration for etcd backups. +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.kmsKeyARN) || has(self.kmsKeyARN)",message="kmsKeyARN cannot be removed once set" +type HCPEtcdBackupS3 struct { + // bucket is the name of the S3 bucket where backups are stored. + // Must be 3-63 characters, lowercase letters, numbers, hyphens, and periods only. + // Must start and end with a letter or number. Consecutive periods are not allowed. + // See https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html + // +required + // +kubebuilder:validation:MinLength=3 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-z0-9][a-z0-9.-]*[a-z0-9]$')",message="bucket must consist of lowercase letters, numbers, hyphens, and periods, and must start and end with a letter or number" + // +kubebuilder:validation:XValidation:rule="!self.contains('..')",message="bucket must not contain consecutive periods" + Bucket string `json:"bucket,omitempty"` + + // region is the AWS region where the S3 bucket is located (e.g. "us-east-1"). + // Must be a valid AWS region identifier: lowercase letters, digits, and hyphens. + // Must start and end with an alphanumeric character, no consecutive hyphens. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-z][a-z0-9-]*[a-z0-9]$')",message="region must consist of lowercase letters, digits, and hyphens, must start with a letter and end with an alphanumeric character" + // +kubebuilder:validation:XValidation:rule="!self.contains('--')",message="region must not contain consecutive hyphens" + Region string `json:"region,omitempty"` + + // keyPrefix is the S3 key prefix for the backup file. + // Must consist of safe S3 object key characters: alphanumeric characters, + // forward slashes, hyphens, underscores, periods, exclamation marks, + // asterisks, single quotes, and parentheses. + // See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9!_.*\\'()/-]+$')",message="keyPrefix must consist of safe S3 key characters: alphanumeric characters, forward slashes, hyphens, underscores, periods, exclamation marks, asterisks, single quotes, and parentheses" + KeyPrefix string `json:"keyPrefix,omitempty"` + + // credentials references a Secret containing AWS credentials for uploading + // to S3. The Secret must exist in the Hypershift Operator namespace and contain a + // 'credentials' key with a valid AWS credentials file. + // +required + Credentials SecretReference `json:"credentials,omitzero"` + + // kmsKeyARN is the ARN of the KMS key used for server-side encryption of the backup. + // Must be a valid AWS KMS key ARN in the format + // "arn::kms:::key/" + // where partition is one of aws, aws-cn, or aws-us-gov. + // This field is immutable once set and cannot be removed. + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.matches('^arn:(aws|aws-cn|aws-us-gov):kms:[a-z0-9-]+:[0-9]{12}:key/[a-zA-Z0-9-]+$')",message="kmsKeyARN must be a valid AWS KMS key ARN (arn::kms:::key/)" + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="kmsKeyARN is immutable" + KMSKeyARN string `json:"kmsKeyARN,omitempty"` +} + +// HCPEtcdBackupAzureBlob defines the Azure Blob storage configuration for etcd backups. +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.encryptionKeyURL) || has(self.encryptionKeyURL)",message="encryptionKeyURL cannot be removed once set" +type HCPEtcdBackupAzureBlob struct { + // container is the name of the Azure Blob container where backups are stored. + // Must be 3-63 characters, lowercase letters, numbers, and hyphens only. + // Must start and end with a letter or number. Consecutive hyphens are not allowed. + // See https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names + // +required + // +kubebuilder:validation:MinLength=3 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$')",message="container must consist of lowercase letters, numbers, and hyphens, and must start and end with a letter or number" + // +kubebuilder:validation:XValidation:rule="!self.contains('--')",message="container must not contain consecutive hyphens" + Container string `json:"container,omitempty"` + + // storageAccount is the name of the Azure Storage Account. + // Must be 3-24 characters, lowercase letters and numbers only. + // See https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview#storage-account-name + // +required + // +kubebuilder:validation:MinLength=3 + // +kubebuilder:validation:MaxLength=24 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-z0-9]+$')",message="storageAccount must consist of lowercase letters and numbers only" + StorageAccount string `json:"storageAccount,omitempty"` + + // keyPrefix is the blob name prefix for the backup file. + // Must consist of valid blob name characters: alphanumeric characters, forward slashes, + // hyphens, underscores, and periods. + // See https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9/_.-]+$')",message="keyPrefix must consist of alphanumeric characters, forward slashes, hyphens, underscores, and periods" + KeyPrefix string `json:"keyPrefix,omitempty"` + + // credentials references a Secret containing Azure credentials for uploading + // to Blob Storage. The Secret must exist in the Hypershift Operator namespace. + // +required + Credentials SecretReference `json:"credentials,omitzero"` + + // encryptionKeyURL is the URL of the Azure Key Vault key used for encryption. + // Must be a valid Azure Key Vault key URL in the format + // "https://.vault.azure.net/keys/[/]". + // This field is immutable once set and cannot be removed. + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=512 + // +kubebuilder:validation:XValidation:rule="isURL(self) && url(self).getScheme() == 'https'",message="encryptionKeyURL must be a valid HTTPS URL" + // +kubebuilder:validation:XValidation:rule="url(self).getHostname().matches('[a-zA-Z0-9-]+\\\\.vault\\\\.azure\\\\.net$')",message="encryptionKeyURL must point to an Azure Key Vault (*.vault.azure.net)" + // +kubebuilder:validation:XValidation:rule="url(self).getEscapedPath().matches('^/keys/[a-zA-Z0-9-]+(/[a-zA-Z0-9]+)?$')",message="encryptionKeyURL path must be /keys/ or /keys//" + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="encryptionKeyURL is immutable" + EncryptionKeyURL string `json:"encryptionKeyURL,omitempty"` +} + +// HCPEtcdBackupStatus defines the observed state of HCPEtcdBackup. +// +kubebuilder:validation:MinProperties=1 +type HCPEtcdBackupStatus struct { + // conditions contains details for the current state of the etcd backup. + // The following condition types are expected: + // - "BackupCompleted": indicates whether the etcd backup has completed (True=success, False=failure). + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=10 + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // snapshotURL is the URL of the completed backup snapshot in cloud storage. + // Must be a valid URL with scheme https or s3. + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=2048 + // +kubebuilder:validation:XValidation:rule="isURL(self)",message="snapshotURL must be a valid URL" + // +kubebuilder:validation:XValidation:rule="url(self).getScheme() == 'https' || url(self).getScheme() == 's3'",message="snapshotURL scheme must be https or s3" + SnapshotURL string `json:"snapshotURL,omitempty"` + + // encryptionMetadata contains metadata about the encryption of the backup. + // When present, at least one platform-specific encryption block must be set. + // +optional + EncryptionMetadata HCPEtcdBackupEncryptionMetadata `json:"encryptionMetadata,omitzero"` +} + +// HCPEtcdBackupEncryptionMetadata contains platform-specific metadata about the +// encryption applied to the backup artifact in cloud storage. +// The presence of a platform block indicates that encryption was applied. +// +kubebuilder:validation:MinProperties=1 +// +kubebuilder:validation:MaxProperties=1 +type HCPEtcdBackupEncryptionMetadata struct { + // aws contains AWS-specific encryption metadata for the backup. + // +optional + AWS HCPEtcdBackupEncryptionMetadataAWS `json:"aws,omitzero"` + + // azure contains Azure-specific encryption metadata for the backup. + // +optional + Azure HCPEtcdBackupEncryptionMetadataAzure `json:"azure,omitzero"` +} + +// HCPEtcdBackupEncryptionMetadataAWS contains AWS-specific encryption metadata. +// The values here reflect the encryption settings from the HCPEtcdBackupConfig input. +type HCPEtcdBackupEncryptionMetadataAWS struct { + // kmsKeyARN is the ARN of the KMS key used for server-side encryption of the backup in S3. + // Must be a valid AWS KMS key ARN in the format + // "arn::kms:::key/" + // where partition is one of aws, aws-cn, or aws-us-gov. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.matches('^arn:(aws|aws-cn|aws-us-gov):kms:[a-z0-9-]+:[0-9]{12}:key/[a-zA-Z0-9-]+$')",message="kmsKeyARN must be a valid AWS KMS key ARN (arn::kms:::key/)" + KMSKeyARN string `json:"kmsKeyARN,omitempty"` +} + +// HCPEtcdBackupEncryptionMetadataAzure contains Azure-specific encryption metadata. +// The values here reflect the encryption settings from the HCPEtcdBackupConfig input. +type HCPEtcdBackupEncryptionMetadataAzure struct { + // encryptionKeyURL is the URL of the Azure Key Vault key used for encryption of the backup. + // Must be a valid Azure Key Vault key URL in the format + // "https://.vault.azure.net/keys/[/]". + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=512 + // +kubebuilder:validation:XValidation:rule="isURL(self) && url(self).getScheme() == 'https'",message="encryptionKeyURL must be a valid HTTPS URL" + // +kubebuilder:validation:XValidation:rule="url(self).getHostname().matches('[a-zA-Z0-9-]+\\\\.vault\\\\.azure\\\\.net$')",message="encryptionKeyURL must point to an Azure Key Vault (*.vault.azure.net)" + // +kubebuilder:validation:XValidation:rule="url(self).getEscapedPath().matches('^/keys/[a-zA-Z0-9-]+(/[a-zA-Z0-9]+)?$')",message="encryptionKeyURL path must be /keys/ or /keys//" + EncryptionKeyURL string `json:"encryptionKeyURL,omitempty"` +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=hcpetcdbackups,scope=Namespaced,shortName=hcpetcdbk +// +kubebuilder:storageversion +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Completed",type="string",JSONPath=".status.conditions[?(@.type==\"BackupCompleted\")].status",description="Backup completion status" +// +kubebuilder:printcolumn:name="URL",type="string",JSONPath=".status.snapshotURL",description="Snapshot URL" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +openshift:enable:FeatureGate=HCPEtcdBackup + +// HCPEtcdBackup represents a request to back up etcd for a hosted control plane. +// This resource is feature-gated behind the HCPEtcdBackup feature gate. +type HCPEtcdBackup struct { + metav1.TypeMeta `json:",inline"` + // metadata is the metadata for the HCPEtcdBackup. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + // spec is the specification for the HCPEtcdBackup. + // +required + Spec HCPEtcdBackupSpec `json:"spec,omitzero"` + // status is the status of the HCPEtcdBackup. + // +optional + Status HCPEtcdBackupStatus `json:"status,omitzero"` +} + +// HCPEtcdBackupList contains a list of HCPEtcdBackup. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type HCPEtcdBackupList struct { + metav1.TypeMeta `json:",inline"` + // metadata is standard list metadata. + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + // items is the list of HCPEtcdBackups. + // +required + Items []HCPEtcdBackup `json:"items,omitempty"` +} + +// HCPEtcdBackupConfigPlatform identifies the cloud platform for backup encryption configuration. +// +kubebuilder:validation:Enum=AWS;Azure +type HCPEtcdBackupConfigPlatform string + +const ( + // AWSBackupConfigPlatform indicates AWS KMS encryption for backup artifacts. + AWSBackupConfigPlatform HCPEtcdBackupConfigPlatform = "AWS" + + // AzureBackupConfigPlatform indicates Azure Key Vault encryption for backup artifacts. + AzureBackupConfigPlatform HCPEtcdBackupConfigPlatform = "Azure" +) + +// HCPEtcdBackupConfig defines the backup encryption configuration that is propagated +// from the HostedCluster to the HostedControlPlane via ManagedEtcdSpec. +// Exactly one platform-specific block must be specified, matching the platform discriminator. +// +union +// +kubebuilder:validation:XValidation:rule="self.platform == 'AWS' ? has(self.aws) : !has(self.aws)",message="aws configuration is required when platform is AWS, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="self.platform == 'Azure' ? has(self.azure) : !has(self.azure)",message="azure configuration is required when platform is Azure, and forbidden otherwise" +type HCPEtcdBackupConfig struct { + // platform specifies the cloud platform for backup encryption configuration. + // Valid values are "AWS" for AWS KMS encryption and "Azure" for Azure Key Vault encryption. + // +unionDiscriminator + // +required + Platform HCPEtcdBackupConfigPlatform `json:"platform,omitempty"` + + // aws contains AWS-specific backup encryption configuration. + // Required when platform is "AWS", and forbidden otherwise. + // +optional + // +unionMember + AWS HCPEtcdBackupConfigAWS `json:"aws,omitzero"` + + // azure contains Azure-specific backup encryption configuration. + // Required when platform is "Azure", and forbidden otherwise. + // +optional + // +unionMember + Azure HCPEtcdBackupConfigAzure `json:"azure,omitzero"` +} + +// HCPEtcdBackupConfigAWS defines AWS-specific encryption settings for etcd backups. +type HCPEtcdBackupConfigAWS struct { + // kmsKeyARN is the ARN of the AWS KMS key to use for encrypting etcd backup artifacts in S3. + // Must be a valid AWS KMS key ARN in the format + // "arn::kms:::key/" + // where partition is one of aws, aws-cn, or aws-us-gov. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.matches('^arn:(aws|aws-cn|aws-us-gov):kms:[a-z0-9-]+:[0-9]{12}:key/[a-zA-Z0-9-]+$')",message="kmsKeyARN must be a valid AWS KMS key ARN (arn::kms:::key/)" + KMSKeyARN string `json:"kmsKeyARN,omitempty"` +} + +// HCPEtcdBackupConfigAzure defines Azure-specific encryption settings for etcd backups. +type HCPEtcdBackupConfigAzure struct { + // encryptionKeyURL is the URL of the Azure Key Vault key to use for encrypting etcd backup artifacts. + // Must be a valid Azure Key Vault key URL in the format + // "https://.vault.azure.net/keys/[/]". + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=512 + // +kubebuilder:validation:XValidation:rule="isURL(self) && url(self).getScheme() == 'https'",message="encryptionKeyURL must be a valid HTTPS URL" + // +kubebuilder:validation:XValidation:rule="url(self).getHostname().matches('[a-zA-Z0-9-]+\\\\.vault\\\\.azure\\\\.net$')",message="encryptionKeyURL must point to an Azure Key Vault (*.vault.azure.net)" + // +kubebuilder:validation:XValidation:rule="url(self).getEscapedPath().matches('^/keys/[a-zA-Z0-9-]+(/[a-zA-Z0-9]+)?$')",message="encryptionKeyURL path must be /keys/ or /keys//" + EncryptionKeyURL string `json:"encryptionKeyURL,omitempty"` +} diff --git a/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml b/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml index 46496a82ea6a..dafa030ea845 100644 --- a/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml +++ b/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-Default.yaml @@ -32,6 +32,9 @@ }, { "name": "GCPPlatform" + }, + { + "name": "HCPEtcdBackup" } ], "enabled": [ diff --git a/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml b/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml index e6b9829665fd..3fd33ceac830 100644 --- a/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml +++ b/api/hypershift/v1beta1/featuregates/featureGate-Hypershift-TechPreviewNoUpgrade.yaml @@ -42,6 +42,9 @@ }, { "name": "GCPPlatform" + }, + { + "name": "HCPEtcdBackup" } ], "version": "" diff --git a/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml b/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml index c01e9915886e..84bf5a8e1d73 100644 --- a/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml +++ b/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-Default.yaml @@ -32,6 +32,9 @@ }, { "name": "GCPPlatform" + }, + { + "name": "HCPEtcdBackup" } ], "enabled": [ diff --git a/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml b/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml index cea5c43cb51b..479143480aa2 100644 --- a/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml +++ b/api/hypershift/v1beta1/featuregates/featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml @@ -42,6 +42,9 @@ }, { "name": "GCPPlatform" + }, + { + "name": "HCPEtcdBackup" } ], "version": "" diff --git a/api/hypershift/v1beta1/hostedcluster_types.go b/api/hypershift/v1beta1/hostedcluster_types.go index 92e1852fb2ca..a32b846952be 100644 --- a/api/hypershift/v1beta1/hostedcluster_types.go +++ b/api/hypershift/v1beta1/hostedcluster_types.go @@ -1885,6 +1885,13 @@ type ManagedEtcdSpec struct { // storage specifies how etcd data is persisted. // +required Storage ManagedEtcdStorageSpec `json:"storage"` + + // backup defines the backup configuration for managed etcd, including + // optional KMS key settings for artifact encryption in cloud storage. + // This configuration is only used when an HCPEtcdBackup CR exists. + // +optional + // +openshift:enable:FeatureGate=HCPEtcdBackup + Backup HCPEtcdBackupConfig `json:"backup,omitzero"` } // ManagedEtcdStorageType is a storage type for an etcd cluster. diff --git a/api/hypershift/v1beta1/zz_generated.deepcopy.go b/api/hypershift/v1beta1/zz_generated.deepcopy.go index c1d7cc029c95..404f1ad6cef5 100644 --- a/api/hypershift/v1beta1/zz_generated.deepcopy.go +++ b/api/hypershift/v1beta1/zz_generated.deepcopy.go @@ -1822,6 +1822,247 @@ func (in *GCPWorkloadIdentityConfig) DeepCopy() *GCPWorkloadIdentityConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackup) DeepCopyInto(out *HCPEtcdBackup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackup. +func (in *HCPEtcdBackup) DeepCopy() *HCPEtcdBackup { + if in == nil { + return nil + } + out := new(HCPEtcdBackup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HCPEtcdBackup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupAzureBlob) DeepCopyInto(out *HCPEtcdBackupAzureBlob) { + *out = *in + out.Credentials = in.Credentials +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupAzureBlob. +func (in *HCPEtcdBackupAzureBlob) DeepCopy() *HCPEtcdBackupAzureBlob { + if in == nil { + return nil + } + out := new(HCPEtcdBackupAzureBlob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupConfig) DeepCopyInto(out *HCPEtcdBackupConfig) { + *out = *in + out.AWS = in.AWS + out.Azure = in.Azure +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupConfig. +func (in *HCPEtcdBackupConfig) DeepCopy() *HCPEtcdBackupConfig { + if in == nil { + return nil + } + out := new(HCPEtcdBackupConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupConfigAWS) DeepCopyInto(out *HCPEtcdBackupConfigAWS) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupConfigAWS. +func (in *HCPEtcdBackupConfigAWS) DeepCopy() *HCPEtcdBackupConfigAWS { + if in == nil { + return nil + } + out := new(HCPEtcdBackupConfigAWS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupConfigAzure) DeepCopyInto(out *HCPEtcdBackupConfigAzure) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupConfigAzure. +func (in *HCPEtcdBackupConfigAzure) DeepCopy() *HCPEtcdBackupConfigAzure { + if in == nil { + return nil + } + out := new(HCPEtcdBackupConfigAzure) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupEncryptionMetadata) DeepCopyInto(out *HCPEtcdBackupEncryptionMetadata) { + *out = *in + out.AWS = in.AWS + out.Azure = in.Azure +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupEncryptionMetadata. +func (in *HCPEtcdBackupEncryptionMetadata) DeepCopy() *HCPEtcdBackupEncryptionMetadata { + if in == nil { + return nil + } + out := new(HCPEtcdBackupEncryptionMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupEncryptionMetadataAWS) DeepCopyInto(out *HCPEtcdBackupEncryptionMetadataAWS) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupEncryptionMetadataAWS. +func (in *HCPEtcdBackupEncryptionMetadataAWS) DeepCopy() *HCPEtcdBackupEncryptionMetadataAWS { + if in == nil { + return nil + } + out := new(HCPEtcdBackupEncryptionMetadataAWS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupEncryptionMetadataAzure) DeepCopyInto(out *HCPEtcdBackupEncryptionMetadataAzure) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupEncryptionMetadataAzure. +func (in *HCPEtcdBackupEncryptionMetadataAzure) DeepCopy() *HCPEtcdBackupEncryptionMetadataAzure { + if in == nil { + return nil + } + out := new(HCPEtcdBackupEncryptionMetadataAzure) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupList) DeepCopyInto(out *HCPEtcdBackupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HCPEtcdBackup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupList. +func (in *HCPEtcdBackupList) DeepCopy() *HCPEtcdBackupList { + if in == nil { + return nil + } + out := new(HCPEtcdBackupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HCPEtcdBackupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupS3) DeepCopyInto(out *HCPEtcdBackupS3) { + *out = *in + out.Credentials = in.Credentials +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupS3. +func (in *HCPEtcdBackupS3) DeepCopy() *HCPEtcdBackupS3 { + if in == nil { + return nil + } + out := new(HCPEtcdBackupS3) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupSpec) DeepCopyInto(out *HCPEtcdBackupSpec) { + *out = *in + out.Storage = in.Storage +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupSpec. +func (in *HCPEtcdBackupSpec) DeepCopy() *HCPEtcdBackupSpec { + if in == nil { + return nil + } + out := new(HCPEtcdBackupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupStatus) DeepCopyInto(out *HCPEtcdBackupStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.EncryptionMetadata = in.EncryptionMetadata +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupStatus. +func (in *HCPEtcdBackupStatus) DeepCopy() *HCPEtcdBackupStatus { + if in == nil { + return nil + } + out := new(HCPEtcdBackupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HCPEtcdBackupStorage) DeepCopyInto(out *HCPEtcdBackupStorage) { + *out = *in + out.S3 = in.S3 + out.AzureBlob = in.AzureBlob +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HCPEtcdBackupStorage. +func (in *HCPEtcdBackupStorage) DeepCopy() *HCPEtcdBackupStorage { + if in == nil { + return nil + } + out := new(HCPEtcdBackupStorage) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostedCluster) DeepCopyInto(out *HostedCluster) { *out = *in @@ -2950,6 +3191,7 @@ func (in *ManagedAzureKeyVault) DeepCopy() *ManagedAzureKeyVault { func (in *ManagedEtcdSpec) DeepCopyInto(out *ManagedEtcdSpec) { *out = *in in.Storage.DeepCopyInto(&out.Storage) + out.Backup = in.Backup } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedEtcdSpec. @@ -3945,6 +4187,21 @@ func (in *SecretEncryptionSpec) DeepCopy() *SecretEncryptionSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretReference) DeepCopyInto(out *SecretReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference. +func (in *SecretReference) DeepCopy() *SecretReference { + if in == nil { + return nil + } + out := new(SecretReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceNetworkEntry) DeepCopyInto(out *ServiceNetworkEntry) { *out = *in diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml index db8ddc7b22e1..55961c584029 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests.yaml @@ -125,6 +125,41 @@ gcpprivateserviceconnects.hypershift.openshift.io: - GCPPlatform Version: v1beta1 +hcpetcdbackups.hypershift.openshift.io: + Annotations: {} + ApprovedPRNumber: "" + CRDName: hcpetcdbackups.hypershift.openshift.io + Capability: "" + Category: "" + FeatureGates: + - HCPEtcdBackup + FilenameOperatorName: "" + FilenameOperatorOrdering: "" + FilenameRunLevel: "" + GroupName: hypershift.openshift.io + HasStatus: true + KindName: HCPEtcdBackup + Labels: {} + PluralName: hcpetcdbackups + PrinterColumns: + - description: Backup completion status + jsonPath: .status.conditions[?(@.type=="BackupCompleted")].status + name: Completed + type: string + - description: Snapshot URL + jsonPath: .status.snapshotURL + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + Scope: Namespaced + ShortNames: + - hcpetcdbk + TopLevelFeatureGates: + - HCPEtcdBackup + Version: v1beta1 + hostedclusters.hypershift.openshift.io: Annotations: {} ApprovedPRNumber: "" @@ -139,6 +174,7 @@ hostedclusters.hypershift.openshift.io: - ExternalOIDCWithUIDAndExtraClaimMappings - ExternalOIDCWithUpstreamParity - GCPPlatform + - HCPEtcdBackup - HyperShiftOnlyDynamicResourceAllocation - ImageStreamImportMode - KMSEncryptionProvider @@ -198,6 +234,7 @@ hostedcontrolplanes.hypershift.openshift.io: - ExternalOIDCWithUIDAndExtraClaimMappings - ExternalOIDCWithUpstreamParity - GCPPlatform + - HCPEtcdBackup - HyperShiftOnlyDynamicResourceAllocation - ImageStreamImportMode - KMSEncryptionProvider diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hcpetcdbackups.hypershift.openshift.io/HCPEtcdBackup.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hcpetcdbackups.hypershift.openshift.io/HCPEtcdBackup.yaml new file mode 100644 index 000000000000..4b04cd79d9be --- /dev/null +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hcpetcdbackups.hypershift.openshift.io/HCPEtcdBackup.yaml @@ -0,0 +1,419 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + feature-gate.release.openshift.io/HCPEtcdBackup: "true" + name: hcpetcdbackups.hypershift.openshift.io +spec: + group: hypershift.openshift.io + names: + kind: HCPEtcdBackup + listKind: HCPEtcdBackupList + plural: hcpetcdbackups + shortNames: + - hcpetcdbk + singular: hcpetcdbackup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Backup completion status + jsonPath: .status.conditions[?(@.type=="BackupCompleted")].status + name: Completed + type: string + - description: Snapshot URL + jsonPath: .status.snapshotURL + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HCPEtcdBackup represents a request to back up etcd for a hosted control plane. + This resource is feature-gated behind the HCPEtcdBackup feature gate. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is the specification for the HCPEtcdBackup. + properties: + storage: + description: storage defines the cloud storage backend where the etcd + snapshot will be uploaded. + properties: + azureBlob: + description: |- + azureBlob specifies the Azure Blob storage configuration for the etcd backup. + Required when storageType is "AzureBlob", and forbidden otherwise. + properties: + container: + description: |- + container is the name of the Azure Blob container where backups are stored. + Must be 3-63 characters, lowercase letters, numbers, and hyphens only. + Must start and end with a letter or number. Consecutive hyphens are not allowed. + See https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names + maxLength: 63 + minLength: 3 + type: string + x-kubernetes-validations: + - message: container must consist of lowercase letters, numbers, + and hyphens, and must start and end with a letter or number + rule: self.matches('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$') + - message: container must not contain consecutive hyphens + rule: '!self.contains(''--'')' + credentials: + description: |- + credentials references a Secret containing Azure credentials for uploading + to Blob Storage. The Secret must exist in the Hypershift Operator namespace. + properties: + name: + description: |- + name is the name of the Secret. It must be a valid DNS-1123 subdomain: at most + 253 characters, consisting of lowercase alphanumeric characters, hyphens, and periods. + Each period-separated segment must start and end with an alphanumeric character. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must consist only of lowercase alphanumeric + characters, hyphens, and periods. Each period-separated + segment must start and end with an alphanumeric character. + rule: self.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$') + required: + - name + type: object + encryptionKeyURL: + description: |- + encryptionKeyURL is the URL of the Azure Key Vault key used for encryption. + Must be a valid Azure Key Vault key URL in the format + "https://.vault.azure.net/keys/[/]". + This field is immutable once set and cannot be removed. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: encryptionKeyURL must be a valid HTTPS URL + rule: isURL(self) && url(self).getScheme() == 'https' + - message: encryptionKeyURL must point to an Azure Key Vault + (*.vault.azure.net) + rule: url(self).getHostname().matches('[a-zA-Z0-9-]+\\.vault\\.azure\\.net$') + - message: encryptionKeyURL path must be /keys/ + or /keys// + rule: url(self).getEscapedPath().matches('^/keys/[a-zA-Z0-9-]+(/[a-zA-Z0-9]+)?$') + - message: encryptionKeyURL is immutable + rule: self == oldSelf + keyPrefix: + description: |- + keyPrefix is the blob name prefix for the backup file. + Must consist of valid blob name characters: alphanumeric characters, forward slashes, + hyphens, underscores, and periods. + See https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names + maxLength: 1024 + minLength: 1 + type: string + x-kubernetes-validations: + - message: keyPrefix must consist of alphanumeric characters, + forward slashes, hyphens, underscores, and periods + rule: self.matches('^[a-zA-Z0-9/_.-]+$') + storageAccount: + description: |- + storageAccount is the name of the Azure Storage Account. + Must be 3-24 characters, lowercase letters and numbers only. + See https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview#storage-account-name + maxLength: 24 + minLength: 3 + type: string + x-kubernetes-validations: + - message: storageAccount must consist of lowercase letters + and numbers only + rule: self.matches('^[a-z0-9]+$') + required: + - container + - credentials + - keyPrefix + - storageAccount + type: object + x-kubernetes-validations: + - message: encryptionKeyURL cannot be removed once set + rule: '!has(oldSelf.encryptionKeyURL) || has(self.encryptionKeyURL)' + s3: + description: |- + s3 specifies the S3 storage configuration for the etcd backup. + Required when storageType is "S3", and forbidden otherwise. + properties: + bucket: + description: |- + bucket is the name of the S3 bucket where backups are stored. + Must be 3-63 characters, lowercase letters, numbers, hyphens, and periods only. + Must start and end with a letter or number. Consecutive periods are not allowed. + See https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html + maxLength: 63 + minLength: 3 + type: string + x-kubernetes-validations: + - message: bucket must consist of lowercase letters, numbers, + hyphens, and periods, and must start and end with a letter + or number + rule: self.matches('^[a-z0-9][a-z0-9.-]*[a-z0-9]$') + - message: bucket must not contain consecutive periods + rule: '!self.contains(''..'')' + credentials: + description: |- + credentials references a Secret containing AWS credentials for uploading + to S3. The Secret must exist in the Hypershift Operator namespace and contain a + 'credentials' key with a valid AWS credentials file. + properties: + name: + description: |- + name is the name of the Secret. It must be a valid DNS-1123 subdomain: at most + 253 characters, consisting of lowercase alphanumeric characters, hyphens, and periods. + Each period-separated segment must start and end with an alphanumeric character. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: name must consist only of lowercase alphanumeric + characters, hyphens, and periods. Each period-separated + segment must start and end with an alphanumeric character. + rule: self.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$') + required: + - name + type: object + keyPrefix: + description: |- + keyPrefix is the S3 key prefix for the backup file. + Must consist of safe S3 object key characters: alphanumeric characters, + forward slashes, hyphens, underscores, periods, exclamation marks, + asterisks, single quotes, and parentheses. + See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html + maxLength: 1024 + minLength: 1 + type: string + x-kubernetes-validations: + - message: 'keyPrefix must consist of safe S3 key characters: + alphanumeric characters, forward slashes, hyphens, underscores, + periods, exclamation marks, asterisks, single quotes, + and parentheses' + rule: self.matches('^[a-zA-Z0-9!_.*\'()/-]+$') + kmsKeyARN: + description: |- + kmsKeyARN is the ARN of the KMS key used for server-side encryption of the backup. + Must be a valid AWS KMS key ARN in the format + "arn::kms:::key/" + where partition is one of aws, aws-cn, or aws-us-gov. + This field is immutable once set and cannot be removed. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: kmsKeyARN must be a valid AWS KMS key ARN (arn::kms:::key/) + rule: self.matches('^arn:(aws|aws-cn|aws-us-gov):kms:[a-z0-9-]+:[0-9]{12}:key/[a-zA-Z0-9-]+$') + - message: kmsKeyARN is immutable + rule: self == oldSelf + region: + description: |- + region is the AWS region where the S3 bucket is located (e.g. "us-east-1"). + Must be a valid AWS region identifier: lowercase letters, digits, and hyphens. + Must start and end with an alphanumeric character, no consecutive hyphens. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: region must consist of lowercase letters, digits, + and hyphens, must start with a letter and end with an + alphanumeric character + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + - message: region must not contain consecutive hyphens + rule: '!self.contains(''--'')' + required: + - bucket + - credentials + - keyPrefix + - region + type: object + x-kubernetes-validations: + - message: kmsKeyARN cannot be removed once set + rule: '!has(oldSelf.kmsKeyARN) || has(self.kmsKeyARN)' + storageType: + description: |- + storageType specifies the type of cloud storage backend for the etcd backup. + Valid values are "S3" for AWS S3 storage and "AzureBlob" for Azure Blob Storage. + enum: + - S3 + - AzureBlob + type: string + required: + - storageType + type: object + x-kubernetes-validations: + - message: s3 configuration is required when storageType is S3, and + forbidden otherwise + rule: 'self.storageType == ''S3'' ? has(self.s3) : !has(self.s3)' + - message: azureBlob configuration is required when storageType is + AzureBlob, and forbidden otherwise + rule: 'self.storageType == ''AzureBlob'' ? has(self.azureBlob) : + !has(self.azureBlob)' + required: + - storage + type: object + x-kubernetes-validations: + - message: HCPEtcdBackupSpec is immutable + rule: self == oldSelf + status: + description: status is the status of the HCPEtcdBackup. + minProperties: 1 + properties: + conditions: + description: |- + conditions contains details for the current state of the etcd backup. + The following condition types are expected: + - "BackupCompleted": indicates whether the etcd backup has completed (True=success, False=failure). + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + encryptionMetadata: + description: |- + encryptionMetadata contains metadata about the encryption of the backup. + When present, at least one platform-specific encryption block must be set. + maxProperties: 1 + minProperties: 1 + properties: + aws: + description: aws contains AWS-specific encryption metadata for + the backup. + properties: + kmsKeyARN: + description: |- + kmsKeyARN is the ARN of the KMS key used for server-side encryption of the backup in S3. + Must be a valid AWS KMS key ARN in the format + "arn::kms:::key/" + where partition is one of aws, aws-cn, or aws-us-gov. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: kmsKeyARN must be a valid AWS KMS key ARN (arn::kms:::key/) + rule: self.matches('^arn:(aws|aws-cn|aws-us-gov):kms:[a-z0-9-]+:[0-9]{12}:key/[a-zA-Z0-9-]+$') + required: + - kmsKeyARN + type: object + azure: + description: azure contains Azure-specific encryption metadata + for the backup. + properties: + encryptionKeyURL: + description: |- + encryptionKeyURL is the URL of the Azure Key Vault key used for encryption of the backup. + Must be a valid Azure Key Vault key URL in the format + "https://.vault.azure.net/keys/[/]". + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: encryptionKeyURL must be a valid HTTPS URL + rule: isURL(self) && url(self).getScheme() == 'https' + - message: encryptionKeyURL must point to an Azure Key Vault + (*.vault.azure.net) + rule: url(self).getHostname().matches('[a-zA-Z0-9-]+\\.vault\\.azure\\.net$') + - message: encryptionKeyURL path must be /keys/ + or /keys// + rule: url(self).getEscapedPath().matches('^/keys/[a-zA-Z0-9-]+(/[a-zA-Z0-9]+)?$') + required: + - encryptionKeyURL + type: object + type: object + snapshotURL: + description: |- + snapshotURL is the URL of the completed backup snapshot in cloud storage. + Must be a valid URL with scheme https or s3. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: snapshotURL must be a valid URL + rule: isURL(self) + - message: snapshotURL scheme must be https or s3 + rule: url(self).getScheme() == 'https' || url(self).getScheme() + == 's3' + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml new file mode 100644 index 000000000000..281868c713f7 --- /dev/null +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml @@ -0,0 +1,6673 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + feature-gate.release.openshift.io/HCPEtcdBackup: "true" + name: hostedclusters.hypershift.openshift.io +spec: + group: hypershift.openshift.io + names: + kind: HostedCluster + listKind: HostedClusterList + plural: hostedclusters + shortNames: + - hc + - hcs + singular: hostedcluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Version + jsonPath: .status.version.history[?(@.state=="Completed")].version + name: Version + type: string + - description: KubeConfig Secret + jsonPath: .status.kubeconfig.name + name: KubeConfig + type: string + - description: Progress + jsonPath: .status.version.history[?(@.state!="")].state + name: Progress + type: string + - description: Available + jsonPath: .status.conditions[?(@.type=="Available")].status + name: Available + type: string + - description: Progressing + jsonPath: .status.conditions[?(@.type=="Progressing")].status + name: Progressing + type: string + - description: Message + jsonPath: .status.conditions[?(@.type=="Available")].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HostedCluster is the primary representation of a HyperShift cluster and encapsulates + the control plane and common data plane configuration. Creating a HostedCluster + results in a fully functional OpenShift control plane with no attached nodes. + To support workloads (e.g. pods), a HostedCluster may have one or more associated + NodePool resources. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is the desired behavior of the HostedCluster. + properties: + additionalTrustBundle: + description: |- + additionalTrustBundle is a local reference to a ConfigMap that must have a "ca-bundle.crt" key + whose content must be a PEM-encoded X.509 certificate bundle that will be added to the hosted controlplane and nodes + If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. + This will be part of every payload generated by the controllers for any NodePool of the HostedCluster. + Changing this value will trigger a rollout for all existing NodePools in the cluster. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + auditWebhook: + description: |- + auditWebhook contains metadata for configuring an audit webhook endpoint + for a cluster to process cluster audit events. It references a secret that + contains the webhook information for the audit webhook endpoint. It is a + secret because if the endpoint has mTLS the kubeconfig will contain client + keys. The kubeconfig needs to be stored in the secret with a secret key + name that corresponds to the constant AuditWebhookKubeconfigKey. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + autoscaling: + description: |- + autoscaling specifies auto-scaling behavior that applies to all NodePools + associated with this HostedCluster. + properties: + balancingIgnoredLabels: + description: |- + balancingIgnoredLabels sets "--balancing-ignore-label