diff --git a/api/hiveextension/v1beta1/agentclusterinstall_types.go b/api/hiveextension/v1beta1/agentclusterinstall_types.go index 63560a108b74..5e66f3e286ca 100644 --- a/api/hiveextension/v1beta1/agentclusterinstall_types.go +++ b/api/hiveextension/v1beta1/agentclusterinstall_types.go @@ -231,6 +231,11 @@ type AgentClusterInstallSpec struct { // LoadBalancer defines the load balancer used by the cluster for ingress traffic. // +optional LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` + + // OSStream is the OS stream to use for this cluster (e.g. rhel-9, rhel-10). + // If unset, the default OS stream for the OpenShift version is used. + // +optional + OSStream string `json:"osStream,omitempty"` } // IgnitionEndpoint stores the data to of the custom ignition endpoint. diff --git a/api/v1beta1/agentserviceconfig_types.go b/api/v1beta1/agentserviceconfig_types.go index 9458f210b638..6b54e9921122 100644 --- a/api/v1beta1/agentserviceconfig_types.go +++ b/api/v1beta1/agentserviceconfig_types.go @@ -39,6 +39,13 @@ type OSImage struct { // The CPU architecture of the image (x86_64/arm64/etc). // +optional CPUArchitecture string `json:"cpuArchitecture"` + // OSStream is the OS stream of this image (e.g. rhel-9, rhel-10). + // +optional + OSStream string `json:"osStream,omitempty"` + // DefaultOSStream indicates whether this OS image is the default stream + // for its OpenShift version and CPU architecture. + // +optional + DefaultOSStream *bool `json:"defaultOsStream,omitempty"` } type MustGatherImage struct { diff --git a/api/v1beta1/infraenv_types.go b/api/v1beta1/infraenv_types.go index 7b9fa4329814..0721b6a67728 100644 --- a/api/v1beta1/infraenv_types.go +++ b/api/v1beta1/infraenv_types.go @@ -133,6 +133,12 @@ type InfraEnvSpec struct { // +optional OSImageVersion string `json:"osImageVersion,omitempty"` + // OSStream is the OS stream to use when generating the InfraEnv (e.g. rhel-9, rhel-10). + // If unset and ClusterRef is set, the cluster's OS stream is used. + // Note: OSStream can't be specified along with ClusterRef while creating an InfraEnv. + // +optional + OSStream string `json:"osStream,omitempty"` + // MirrorRegistryRef references a ConfigMap containing mirror registry configuration in TOML format. // The referenced ConfigMap should contain 'registries.conf' and optionally 'ca-bundle.crt' keys. // This configuration is embedded into the discovery image so that agents can pull container images diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 762d63eb0fc3..6bfeb4d93ea5 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -291,7 +291,9 @@ func (in *AgentServiceConfigSpec) DeepCopyInto(out *AgentServiceConfigSpec) { if in.OSImages != nil { in, out := &in.OSImages, &out.OSImages *out = make([]OSImage, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.MustGatherImages != nil { in, out := &in.MustGatherImages, &out.MustGatherImages @@ -1233,6 +1235,11 @@ func (in *NetConfig) DeepCopy() *NetConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OSImage) DeepCopyInto(out *OSImage) { *out = *in + if in.DefaultOSStream != nil { + in, out := &in.DefaultOSStream, &out.DefaultOSStream + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OSImage. diff --git a/api/vendor/github.com/openshift/assisted-service/models/cluster.go b/api/vendor/github.com/openshift/assisted-service/models/cluster.go index 1f54de6a02d7..e529fc724046 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/cluster.go +++ b/api/vendor/github.com/openshift/assisted-service/models/cluster.go @@ -230,6 +230,9 @@ type Cluster struct { // Indication if organization soft timeouts is enabled for the cluster. OrgSoftTimeoutsEnabled bool `json:"org_soft_timeouts_enabled,omitempty"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/api/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go b/api/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go index 0f72526515bd..74802da9c36a 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go +++ b/api/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go @@ -127,6 +127,9 @@ type ClusterCreateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/api/vendor/github.com/openshift/assisted-service/models/infra_env.go b/api/vendor/github.com/openshift/assisted-service/models/infra_env.go index c634fed9d391..418a7199cf49 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/infra_env.go +++ b/api/vendor/github.com/openshift/assisted-service/models/infra_env.go @@ -94,6 +94,9 @@ type InfraEnv struct { // org id OrgID string `json:"org_id,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/api/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go b/api/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go index c8c3e2befc76..210fd9310cec 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go +++ b/api/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go @@ -63,6 +63,9 @@ type InfraEnvCreateParams struct { // Version of the OpenShift cluster (used to infer the RHCOS version - temporary until generic logic implemented). OpenshiftVersion string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/api/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go b/api/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go index 08c46bdf87a1..fa33b70cbf46 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go +++ b/api/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go @@ -47,6 +47,9 @@ type InfraEnvUpdateParams struct { // Version of the OS image OpenshiftVersion *string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream *string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/api/vendor/github.com/openshift/assisted-service/models/os_image.go b/api/vendor/github.com/openshift/assisted-service/models/os_image.go index 5d7cf67d8fe1..58cf88d3aefa 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/os_image.go +++ b/api/vendor/github.com/openshift/assisted-service/models/os_image.go @@ -25,11 +25,17 @@ type OsImage struct { // Enum: [x86_64 aarch64 arm64 ppc64le s390x] CPUArchitecture *string `json:"cpu_architecture" gorm:"default:'x86_64'"` + // Whether this OS image is the default stream for its OpenShift version and CPU architecture. + DefaultOsStream *bool `json:"default_os_stream,omitempty"` + // Version of the operating system image // Example: 4.12 // Required: true OpenshiftVersion *string `json:"openshift_version"` + // The OS stream of this image (e.g. rhel-9, rhel-10). + OsStream *string `json:"os_stream,omitempty"` + // The base OS image used for the discovery iso. // Required: true URL *string `json:"url"` diff --git a/api/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go b/api/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go index 979f98035982..0c9f0b310af1 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go +++ b/api/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go @@ -116,6 +116,9 @@ type V2ClusterUpdateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream *string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/cluster.go b/client/vendor/github.com/openshift/assisted-service/models/cluster.go index 1f54de6a02d7..e529fc724046 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/cluster.go +++ b/client/vendor/github.com/openshift/assisted-service/models/cluster.go @@ -230,6 +230,9 @@ type Cluster struct { // Indication if organization soft timeouts is enabled for the cluster. OrgSoftTimeoutsEnabled bool `json:"org_soft_timeouts_enabled,omitempty"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go b/client/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go index 0f72526515bd..74802da9c36a 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go +++ b/client/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go @@ -127,6 +127,9 @@ type ClusterCreateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/infra_env.go b/client/vendor/github.com/openshift/assisted-service/models/infra_env.go index c634fed9d391..418a7199cf49 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/infra_env.go +++ b/client/vendor/github.com/openshift/assisted-service/models/infra_env.go @@ -94,6 +94,9 @@ type InfraEnv struct { // org id OrgID string `json:"org_id,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go b/client/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go index c8c3e2befc76..210fd9310cec 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go +++ b/client/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go @@ -63,6 +63,9 @@ type InfraEnvCreateParams struct { // Version of the OpenShift cluster (used to infer the RHCOS version - temporary until generic logic implemented). OpenshiftVersion string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go b/client/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go index 08c46bdf87a1..fa33b70cbf46 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go +++ b/client/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go @@ -47,6 +47,9 @@ type InfraEnvUpdateParams struct { // Version of the OS image OpenshiftVersion *string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream *string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/os_image.go b/client/vendor/github.com/openshift/assisted-service/models/os_image.go index 5d7cf67d8fe1..58cf88d3aefa 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/os_image.go +++ b/client/vendor/github.com/openshift/assisted-service/models/os_image.go @@ -25,11 +25,17 @@ type OsImage struct { // Enum: [x86_64 aarch64 arm64 ppc64le s390x] CPUArchitecture *string `json:"cpu_architecture" gorm:"default:'x86_64'"` + // Whether this OS image is the default stream for its OpenShift version and CPU architecture. + DefaultOsStream *bool `json:"default_os_stream,omitempty"` + // Version of the operating system image // Example: 4.12 // Required: true OpenshiftVersion *string `json:"openshift_version"` + // The OS stream of this image (e.g. rhel-9, rhel-10). + OsStream *string `json:"os_stream,omitempty"` + // The base OS image used for the discovery iso. // Required: true URL *string `json:"url"` diff --git a/client/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go b/client/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go index 979f98035982..0c9f0b310af1 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go +++ b/client/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go @@ -116,6 +116,9 @@ type V2ClusterUpdateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream *string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/cmd/agentbasedinstaller/register.go b/cmd/agentbasedinstaller/register.go index 5c4f15c9e51f..e0fc6ac7ae7e 100644 --- a/cmd/agentbasedinstaller/register.go +++ b/cmd/agentbasedinstaller/register.go @@ -211,7 +211,7 @@ func RegisterInfraEnv(ctx context.Context, log *log.Logger, bmInventory *client. if modelsCluster != nil { clusterID = modelsCluster.ID } - infraEnvParams := controllers.CreateInfraEnvParams(&infraEnv, models.ImageType(imageTypeISO), pullSecret, clusterID, "") + infraEnvParams := controllers.CreateInfraEnvParams(&infraEnv, models.ImageType(imageTypeISO), pullSecret, clusterID, "", "") fileInfo, _ := os.Stat(nmStateConfigPath) if fileInfo != nil { diff --git a/config/crd/bases/agent-install.openshift.io_agentserviceconfigs.yaml b/config/crd/bases/agent-install.openshift.io_agentserviceconfigs.yaml index 503869fc99f9..4860c0fda24d 100644 --- a/config/crd/bases/agent-install.openshift.io_agentserviceconfigs.yaml +++ b/config/crd/bases/agent-install.openshift.io_agentserviceconfigs.yaml @@ -775,11 +775,20 @@ spec: cpuArchitecture: description: The CPU architecture of the image (x86_64/arm64/etc). type: string + defaultOsStream: + description: |- + DefaultOSStream indicates whether this OS image is the default stream + for its OpenShift version and CPU architecture. + type: boolean openshiftVersion: description: |- OpenshiftVersion is the Major.Minor version of OpenShift that this image is to be associated with. type: string + osStream: + description: OSStream is the OS stream of this image (e.g. rhel-9, + rhel-10). + type: string rootFSUrl: description: |- rootFSUrl specifies the path to the root filesystem. diff --git a/config/crd/bases/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml b/config/crd/bases/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml index c72c0d254dd0..a8bab54a0ea2 100644 --- a/config/crd/bases/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml +++ b/config/crd/bases/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml @@ -793,11 +793,20 @@ spec: cpuArchitecture: description: The CPU architecture of the image (x86_64/arm64/etc). type: string + defaultOsStream: + description: |- + DefaultOSStream indicates whether this OS image is the default stream + for its OpenShift version and CPU architecture. + type: boolean openshiftVersion: description: |- OpenshiftVersion is the Major.Minor version of OpenShift that this image is to be associated with. type: string + osStream: + description: OSStream is the OS stream of this image (e.g. rhel-9, + rhel-10). + type: string rootFSUrl: description: |- rootFSUrl specifies the path to the root filesystem. diff --git a/config/crd/bases/agent-install.openshift.io_infraenvs.yaml b/config/crd/bases/agent-install.openshift.io_infraenvs.yaml index 374fca42d012..1b3a07c9fe18 100644 --- a/config/crd/bases/agent-install.openshift.io_infraenvs.yaml +++ b/config/crd/bases/agent-install.openshift.io_infraenvs.yaml @@ -247,6 +247,12 @@ spec: (i.e. OSImageVersion should equal to an OpenshiftVersion in OSImages list). Note: OSImageVersion can't be specified along with ClusterRef while creating an InfraEnv. type: string + osStream: + description: |- + OSStream is the OS stream to use when generating the InfraEnv (e.g. rhel-9, rhel-10). + If unset and ClusterRef is set, the cluster's OS stream is used. + Note: OSStream can't be specified along with ClusterRef while creating an InfraEnv. + type: string proxy: description: |- Proxy defines the proxy settings for agents and clusters that use the InfraEnv. If diff --git a/config/crd/bases/extensions.hive.openshift.io_agentclusterinstalls.yaml b/config/crd/bases/extensions.hive.openshift.io_agentclusterinstalls.yaml index 2343bdfda6cb..d2aa21ec560c 100644 --- a/config/crd/bases/extensions.hive.openshift.io_agentclusterinstalls.yaml +++ b/config/crd/bases/extensions.hive.openshift.io_agentclusterinstalls.yaml @@ -533,6 +533,11 @@ spec: For single-node installations (none or external platform), set to true or leave empty. type: boolean type: object + osStream: + description: |- + OSStream is the OS stream to use for this cluster (e.g. rhel-9, rhel-10). + If unset, the default OS stream for the OpenShift version is used. + type: string platformType: description: PlatformType is the name for the specific platform upon which to perform the installation. diff --git a/config/crd/resources.yaml b/config/crd/resources.yaml index d0f070090887..d779c672deaf 100644 --- a/config/crd/resources.yaml +++ b/config/crd/resources.yaml @@ -648,6 +648,11 @@ spec: For single-node installations (none or external platform), set to true or leave empty. type: boolean type: object + osStream: + description: |- + OSStream is the OS stream to use for this cluster (e.g. rhel-9, rhel-10). + If unset, the default OS stream for the OpenShift version is used. + type: string platformType: description: PlatformType is the name for the specific platform upon which to perform the installation. @@ -2218,11 +2223,20 @@ spec: cpuArchitecture: description: The CPU architecture of the image (x86_64/arm64/etc). type: string + defaultOsStream: + description: |- + DefaultOSStream indicates whether this OS image is the default stream + for its OpenShift version and CPU architecture. + type: boolean openshiftVersion: description: |- OpenshiftVersion is the Major.Minor version of OpenShift that this image is to be associated with. type: string + osStream: + description: OSStream is the OS stream of this image (e.g. rhel-9, + rhel-10). + type: string rootFSUrl: description: |- rootFSUrl specifies the path to the root filesystem. @@ -3090,11 +3104,20 @@ spec: cpuArchitecture: description: The CPU architecture of the image (x86_64/arm64/etc). type: string + defaultOsStream: + description: |- + DefaultOSStream indicates whether this OS image is the default stream + for its OpenShift version and CPU architecture. + type: boolean openshiftVersion: description: |- OpenshiftVersion is the Major.Minor version of OpenShift that this image is to be associated with. type: string + osStream: + description: OSStream is the OS stream of this image (e.g. rhel-9, + rhel-10). + type: string rootFSUrl: description: |- rootFSUrl specifies the path to the root filesystem. @@ -3422,6 +3445,12 @@ spec: (i.e. OSImageVersion should equal to an OpenshiftVersion in OSImages list). Note: OSImageVersion can't be specified along with ClusterRef while creating an InfraEnv. type: string + osStream: + description: |- + OSStream is the OS stream to use when generating the InfraEnv (e.g. rhel-9, rhel-10). + If unset and ClusterRef is set, the cluster's OS stream is used. + Note: OSStream can't be specified along with ClusterRef while creating an InfraEnv. + type: string proxy: description: |- Proxy defines the proxy settings for agents and clusters that use the InfraEnv. If diff --git a/deploy/olm-catalog/manifests/agent-install.openshift.io_agentserviceconfigs.yaml b/deploy/olm-catalog/manifests/agent-install.openshift.io_agentserviceconfigs.yaml index 490b70e97352..0bd2a52e3a6c 100644 --- a/deploy/olm-catalog/manifests/agent-install.openshift.io_agentserviceconfigs.yaml +++ b/deploy/olm-catalog/manifests/agent-install.openshift.io_agentserviceconfigs.yaml @@ -775,11 +775,20 @@ spec: cpuArchitecture: description: The CPU architecture of the image (x86_64/arm64/etc). type: string + defaultOsStream: + description: |- + DefaultOSStream indicates whether this OS image is the default stream + for its OpenShift version and CPU architecture. + type: boolean openshiftVersion: description: |- OpenshiftVersion is the Major.Minor version of OpenShift that this image is to be associated with. type: string + osStream: + description: OSStream is the OS stream of this image (e.g. rhel-9, + rhel-10). + type: string rootFSUrl: description: |- rootFSUrl specifies the path to the root filesystem. diff --git a/deploy/olm-catalog/manifests/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml b/deploy/olm-catalog/manifests/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml index 578569bc823a..234221403e52 100644 --- a/deploy/olm-catalog/manifests/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml +++ b/deploy/olm-catalog/manifests/agent-install.openshift.io_hypershiftagentserviceconfigs.yaml @@ -793,11 +793,20 @@ spec: cpuArchitecture: description: The CPU architecture of the image (x86_64/arm64/etc). type: string + defaultOsStream: + description: |- + DefaultOSStream indicates whether this OS image is the default stream + for its OpenShift version and CPU architecture. + type: boolean openshiftVersion: description: |- OpenshiftVersion is the Major.Minor version of OpenShift that this image is to be associated with. type: string + osStream: + description: OSStream is the OS stream of this image (e.g. rhel-9, + rhel-10). + type: string rootFSUrl: description: |- rootFSUrl specifies the path to the root filesystem. diff --git a/deploy/olm-catalog/manifests/agent-install.openshift.io_infraenvs.yaml b/deploy/olm-catalog/manifests/agent-install.openshift.io_infraenvs.yaml index 9bd0922f0a73..8a38d5d7209b 100644 --- a/deploy/olm-catalog/manifests/agent-install.openshift.io_infraenvs.yaml +++ b/deploy/olm-catalog/manifests/agent-install.openshift.io_infraenvs.yaml @@ -257,6 +257,12 @@ spec: (i.e. OSImageVersion should equal to an OpenshiftVersion in OSImages list). Note: OSImageVersion can't be specified along with ClusterRef while creating an InfraEnv. type: string + osStream: + description: |- + OSStream is the OS stream to use when generating the InfraEnv (e.g. rhel-9, rhel-10). + If unset and ClusterRef is set, the cluster's OS stream is used. + Note: OSStream can't be specified along with ClusterRef while creating an InfraEnv. + type: string proxy: description: |- Proxy defines the proxy settings for agents and clusters that use the InfraEnv. If diff --git a/deploy/olm-catalog/manifests/extensions.hive.openshift.io_agentclusterinstalls.yaml b/deploy/olm-catalog/manifests/extensions.hive.openshift.io_agentclusterinstalls.yaml index 7aed2907fdf9..65641043456d 100644 --- a/deploy/olm-catalog/manifests/extensions.hive.openshift.io_agentclusterinstalls.yaml +++ b/deploy/olm-catalog/manifests/extensions.hive.openshift.io_agentclusterinstalls.yaml @@ -530,6 +530,11 @@ spec: For single-node installations (none or external platform), set to true or leave empty. type: boolean type: object + osStream: + description: |- + OSStream is the OS stream to use for this cluster (e.g. rhel-9, rhel-10). + If unset, the default OS stream for the OpenShift version is used. + type: string platformType: description: PlatformType is the name for the specific platform upon which to perform the installation. diff --git a/internal/bminventory/inventory.go b/internal/bminventory/inventory.go index 99938d60260b..649b7e167e27 100644 --- a/internal/bminventory/inventory.go +++ b/internal/bminventory/inventory.go @@ -601,10 +601,11 @@ func (b *bareMetalInventory) getNewClusterReleaseImage(ctx context.Context, para if len(releaseImage.CPUArchitectures) == 1 { releaseVersion := *releaseImage.OpenshiftVersion releaseArch := releaseImage.CPUArchitectures[0] + osStream := params.OsStream var osImage *models.OsImage - osImage, err = b.osImages.GetOsImage(releaseVersion, releaseArch) + osImage, err = b.osImages.GetOsImage(releaseVersion, releaseArch, osStream) if err != nil || osImage.URL == nil { - return nil, errors.Errorf("No OS images are available for version %s and architecture %s", releaseVersion, releaseArch) + return nil, errors.Errorf("No OS images are available for version (%s), CPU architecture (%s) and os stream (%s)", releaseVersion, releaseArch, osStream) } } @@ -806,6 +807,7 @@ func (b *bareMetalInventory) RegisterClusterInternal(ctx context.Context, kubeKe OrgSoftTimeoutsEnabled: orgSoftTimeoutsEnabled, ControlPlaneCount: swag.Int64Value(params.NewClusterParams.ControlPlaneCount), LoadBalancer: params.NewClusterParams.LoadBalancer, + OsStream: params.NewClusterParams.OsStream, }, KubeKeyName: kubeKey.Name, KubeKeyNamespace: kubeKey.Namespace, @@ -1287,16 +1289,14 @@ func (b *bareMetalInventory) updateExternalImageInfo(ctx context.Context, infraE updates["type"] = imageType infraEnv.Type = common.ImageTypePtr(imageType) - osImage, err := b.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture) + osImage, err := b.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, infraEnv.OsStream) if err != nil { return common.NewApiError(http.StatusBadRequest, err) } - var version string - if osImage.OpenshiftVersion != nil { - version = *osImage.OpenshiftVersion - } else { - return errors.Errorf("OS image entry '%+v' missing OpenshiftVersion field", osImage) + version, err := imageservice.OsImageVersion(osImage) + if err != nil { + return err } var arch string @@ -2673,6 +2673,7 @@ func (b *bareMetalInventory) updateClusterData(_ context.Context, cluster *commo optionalParam(params.ClusterUpdateParams.NoProxy, "no_proxy", updates) optionalParam(params.ClusterUpdateParams.SSHPublicKey, "ssh_public_key", updates) optionalParam(params.ClusterUpdateParams.Hyperthreading, "hyperthreading", updates) + optionalParam(params.ClusterUpdateParams.OsStream, "os_stream", updates) b.setProxyUsage(params.ClusterUpdateParams.HTTPProxy, params.ClusterUpdateParams.HTTPSProxy, params.ClusterUpdateParams.NoProxy, usages) @@ -5228,10 +5229,14 @@ func (b *bareMetalInventory) RegisterInfraEnvInternal(ctx context.Context, kubeK } openshiftVersion := params.InfraenvCreateParams.OpenshiftVersion + osStream := params.InfraenvCreateParams.OsStream + if cluster != nil && osStream == "" { + osStream = cluster.OsStream + } if b.EnableImageService { var osImage *models.OsImage - osImage, err = b.osImages.GetOsImageOrLatest(params.InfraenvCreateParams.OpenshiftVersion, params.InfraenvCreateParams.CPUArchitecture) + osImage, err = b.osImages.GetOsImageOrLatest(params.InfraenvCreateParams.OpenshiftVersion, params.InfraenvCreateParams.CPUArchitecture, osStream) if err != nil { return common.NewApiError(http.StatusBadRequest, err) } @@ -5284,6 +5289,7 @@ func (b *bareMetalInventory) RegisterInfraEnvInternal(ctx context.Context, kubeK KernelArguments: kernelArguments, AdditionalTrustBundle: params.InfraenvCreateParams.AdditionalTrustBundle, NetworkDiscoveryDelaySeconds: params.InfraenvCreateParams.NetworkDiscoveryDelaySeconds, + OsStream: osStream, }, KubeKeyNamespace: kubeKey.Namespace, ImageTokenKey: imageTokenKey, @@ -5657,8 +5663,13 @@ func (b *bareMetalInventory) UpdateInfraEnvInternal(ctx context.Context, params openshiftVersion = *params.InfraEnvUpdateParams.OpenshiftVersion } + osStream := infraEnv.OsStream + if params.InfraEnvUpdateParams.OsStream != nil { + osStream = *params.InfraEnvUpdateParams.OsStream + } + if b.EnableImageService { - _, err = b.osImages.GetOsImageOrLatest(openshiftVersion, infraEnv.CPUArchitecture) + _, err = b.osImages.GetOsImageOrLatest(openshiftVersion, infraEnv.CPUArchitecture, osStream) if err != nil { return common.NewApiError(http.StatusBadRequest, err) } @@ -5748,6 +5759,11 @@ func (b *bareMetalInventory) updateInfraEnvData(infraEnv *common.InfraEnv, param updates["openshift_version"] = inputVersion } + inputOsStream := swag.StringValue(params.InfraEnvUpdateParams.OsStream) + if inputOsStream != "" && inputOsStream != infraEnv.OsStream { + updates["os_stream"] = inputOsStream + } + if err := b.updateInfraEnvNtpSources(params, infraEnv, updates, log); err != nil { return err } diff --git a/internal/bminventory/inventory_test.go b/internal/bminventory/inventory_test.go index 11862c7a8e74..1998e7aab4d7 100644 --- a/internal/bminventory/inventory_test.go +++ b/internal/bminventory/inventory_test.go @@ -274,7 +274,7 @@ func mockClusterRegisterSteps(withReleaseImageURL bool) { mockVersions.EXPECT().GetReleaseImage(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.ReleaseImage, nil).Times(1) } - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) mockOperatorManager.EXPECT().GetSupportedOperatorsByType(models.OperatorTypeBuiltin).Return([]*models.MonitoredOperator{&common.TestDefaultConfig.MonitoredOperator}).Times(1) mockProviderRegistry.EXPECT().SetPlatformUsages(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) } @@ -335,8 +335,8 @@ func mockClusterUpdateSuccess(times int, hosts int) { func mockInfraEnvRegisterSuccess() { - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockStaticNetworkConfig.EXPECT().FormatStaticNetworkConfigForDB(gomock.Any()).Return("", nil).Times(1) mockSecretValidator.EXPECT().ValidatePullSecret(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).Times(1) @@ -346,8 +346,8 @@ func mockInfraEnvRegisterSuccess() { func mockInfraEnvUpdateSuccess() { - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).Times(1) mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName))).AnyTimes() @@ -2821,7 +2821,7 @@ var _ = Describe("cluster", func() { It("OLM invalid name", func() { newOperatorName := "invalid-name" mockVersions.EXPECT().GetReleaseImage(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.ReleaseImage, nil).Times(1) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) mockOperatorManager.EXPECT().GetSupportedOperatorsByType(models.OperatorTypeBuiltin).Return([]*models.MonitoredOperator{&common.TestDefaultConfig.MonitoredOperator}).Times(1) mockOperatorManager.EXPECT().GetOperatorByName(newOperatorName).Return(nil, errors.Errorf("error")).Times(1) @@ -2837,7 +2837,7 @@ var _ = Describe("cluster", func() { It("should return error when both cnv and lvm operator enabled", func() { mockVersions.EXPECT().GetReleaseImage(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.ReleaseImage, nil).Times(1) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) mockOperatorManager.EXPECT().GetSupportedOperatorsByType(models.OperatorTypeBuiltin).Return([]*models.MonitoredOperator{&common.TestDefaultConfig.MonitoredOperator}).Times(1) mockOperatorManager.EXPECT().ResolveDependencies(gomock.Any(), gomock.Any()). DoAndReturn(func(commonCluster *common.Cluster, operators []*models.MonitoredOperator) ([]*models.MonitoredOperator, error) { @@ -9197,10 +9197,10 @@ var _ = Describe("infraEnvs", func() { }) It("No version specified", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) mockOSImages.EXPECT().GetOsImageOrLatest( *common.TestDefaultConfig.OsImage.OpenshiftVersion, - *common.TestDefaultConfig.OsImage.CPUArchitecture).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + *common.TestDefaultConfig.OsImage.CPUArchitecture, "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) mockStaticNetworkConfig.EXPECT().FormatStaticNetworkConfigForDB(gomock.Any()).Return("", nil).Times(1) mockSecretValidator.EXPECT().ValidatePullSecret(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).Times(1) @@ -9328,8 +9328,8 @@ var _ = Describe("infraEnvs", func() { cluster := createCluster(db, models.ClusterStatusInstallingPendingUserAction) - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockSecretValidator.EXPECT().ValidatePullSecret(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).Times(1) mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( @@ -9364,7 +9364,7 @@ var _ = Describe("infraEnvs", func() { largeDiscoveryIgnition := fmt.Sprintf(`{"ignition": {"version": "3.1.0"}, "storage": {"files": [{"path": "/tmp/example", "contents": {"source": "data:text/plain;base64,%s"}}]}}`, content) override := `{"ignition": {"version": "3.1.0"}, "storage": {"files": [{"path": "/tmp/example", "contents": {"source": "data:text/plain;base64,aGVscGltdHJhcHBlZGluYXN3YWdnZXJzcGVj"}}]}}` - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockStaticNetworkConfig.EXPECT().FormatStaticNetworkConfigForDB(gomock.Any()).Return("", nil).Times(1) mockSecretValidator.EXPECT().ValidatePullSecret(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(largeDiscoveryIgnition, nil).AnyTimes() @@ -9957,8 +9957,8 @@ var _ = Describe("infraEnvs", func() { }) It("Update Image type on s390x architecture", func() { var err error - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName))).AnyTimes() @@ -9977,8 +9977,8 @@ var _ = Describe("infraEnvs", func() { }) It("Update Image type on s390x architecture to full - fail", func() { var err error - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName))).AnyTimes() @@ -10453,7 +10453,7 @@ var _ = Describe("infraEnvs", func() { i, err := bm.GetInfraEnvInternal(ctx, installer.GetInfraEnvParams{InfraEnvID: infraEnvID}) Expect(err).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "", "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, true, bm.authHandler.AuthType(), gomock.Any()).Return("ignitionconfigforlogging", nil).Times(1) mockEvents.EXPECT().SendInfraEnvEvent(ctx, eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -10475,7 +10475,7 @@ var _ = Describe("infraEnvs", func() { pathList := strings.Split(parsed.Path, "/") Expect(pathList).To(HaveLen(8)) Expect(pathList[7]).To(Equal("minimal.iso")) - Expect(pathList[5]).To(Equal(common.TestDefaultConfig.OpenShiftVersion)) + Expect(pathList[5]).To(Equal(*common.TestDefaultConfig.OsImage.Version)) }) It("sets the download url correctly with the image service - bounded InfraEnv", func() { @@ -10496,7 +10496,7 @@ var _ = Describe("infraEnvs", func() { err := db.Create(boundedInfraEnv).Error Expect(err).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "", "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, true, bm.authHandler.AuthType(), gomock.Any()).Return("ignitionconfigforlogging", nil).Times(1) mockEvents.EXPECT().SendInfraEnvEvent(ctx, eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -10518,7 +10518,7 @@ var _ = Describe("infraEnvs", func() { pathList := strings.Split(parsed.Path, "/") Expect(pathList).To(HaveLen(8)) Expect(pathList[7]).To(Equal("minimal.iso")) - Expect(pathList[5]).To(Equal(common.TestDefaultConfig.OpenShiftVersion)) + Expect(pathList[5]).To(Equal(*common.TestDefaultConfig.OsImage.Version)) }) It("fails when the internal ignition config is invalid", func() { @@ -10544,7 +10544,7 @@ var _ = Describe("infraEnvs", func() { i, err := bm.GetInfraEnvInternal(ctx, installer.GetInfraEnvParams{InfraEnvID: infraEnvID}) Expect(err).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "", "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, true, bm.authHandler.AuthType(), gomock.Any()).Return("ignitionconfigforlogging", nil) mockEvents.EXPECT().SendInfraEnvEvent(ctx, eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -10571,7 +10571,7 @@ var _ = Describe("infraEnvs", func() { i, err := bm.GetInfraEnvInternal(ctx, installer.GetInfraEnvParams{InfraEnvID: infraEnvID}) Expect(err).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "", "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, true, bm.authHandler.AuthType(), gomock.Any()).Return("ignitionconfigforlogging", nil) mockEvents.EXPECT().SendInfraEnvEvent(ctx, eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -10621,7 +10621,7 @@ var _ = Describe("infraEnvs", func() { i, err := bm.GetInfraEnvInternal(ctx, installer.GetInfraEnvParams{InfraEnvID: infraEnvID}) Expect(err).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "", "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, true, bm.authHandler.AuthType(), gomock.Any()).Return("ignitionconfigforlogging", nil).Times(1) mockEvents.EXPECT().SendInfraEnvEvent(ctx, eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -10638,7 +10638,7 @@ var _ = Describe("infraEnvs", func() { i, err := bm.GetInfraEnvInternal(ctx, installer.GetInfraEnvParams{InfraEnvID: infraEnvID}) Expect(err).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "", "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, true, bm.authHandler.AuthType(), gomock.Any()).Return("ignitionconfigforlogging", nil).Times(7) mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), bm.IgnitionConfig, false, bm.authHandler.AuthType(), gomock.Any()).Return(discovery_ignition_3_1, nil).AnyTimes() mockEvents.EXPECT().SendInfraEnvEvent(ctx, eventstest.NewEventMatcher( @@ -10774,7 +10774,7 @@ var _ = Describe("infraEnvs", func() { It("Invalid NTP source", func() { ntpSource := "inject'" - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() reply := bm.UpdateInfraEnv(ctx, installer.UpdateInfraEnvParams{ InfraEnvID: infraEnvID, InfraEnvUpdateParams: &models.InfraEnvUpdateParams{ @@ -13371,7 +13371,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { It("returns ipxe-script successfully", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) content := getResponseData("ipxe-script", false, nil, "", infraEnvID) lines := strings.Split(string(content), "\n") @@ -13388,14 +13388,14 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { Expect(err).NotTo(HaveOccurred()) Expect(kernelURL.Host).To(Equal(imageServiceHost)) Expect(kernelURL.Path).To(Equal(imageServicePath + "/boot-artifacts/kernel")) - Expect(kernelURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.OpenshiftVersion)) + Expect(kernelURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.Version)) Expect(kernelURL.Query().Get("arch")).To(Equal(*common.TestDefaultConfig.OsImage.CPUArchitecture)) rootfsURL, err := url.Parse(match[2]) Expect(err).NotTo(HaveOccurred()) Expect(rootfsURL.Host).To(Equal(imageServiceHost)) Expect(rootfsURL.Path).To(Equal(imageServicePath + "/boot-artifacts/rootfs")) - Expect(rootfsURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.OpenshiftVersion)) + Expect(rootfsURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.Version)) Expect(rootfsURL.Query().Get("arch")).To(Equal(*common.TestDefaultConfig.OsImage.CPUArchitecture)) Expect(match[3]).To(Equal(`random.trust_cpu=on rd.luks.options=discard ignition.firstboot ignition.platform.id=metal console=tty1 console=ttyS1,115200n8 coreos.inst.persistent-kargs="console=tty1 console=ttyS1,115200n8"`)) @@ -13410,7 +13410,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { Expect(initrdURL.Scheme).To(Equal("http")) Expect(initrdURL.Host).To(Equal(imageServiceHost)) Expect(initrdURL.Path).To(Equal(fmt.Sprintf("%s/images/%s/pxe-initrd", imageServicePath, infraEnvID))) - Expect(initrdURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.OpenshiftVersion)) + Expect(initrdURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.Version)) Expect(initrdURL.Query().Get("arch")).To(Equal(*common.TestDefaultConfig.OsImage.CPUArchitecture)) Expect(lines[3]).To(Equal("boot")) @@ -13424,7 +13424,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { } initialKernelArguments := `random.trust_cpu=on rd.luks.options=discard ignition.firstboot ignition.platform.id=metal console=tty1 console=ttyS1,115200n8 coreos.inst.persistent-kargs="console=tty1 console=ttyS1,115200n8"` - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() content := getResponseData("ipxe-script", false, nil, "", infraEnvID) lines := strings.Split(string(content), "\n") @@ -13459,7 +13459,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { It("returns ipxe-script successfully with mac", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) content := getResponseData("ipxe-script", true, nil, "", infraEnvID) lines := strings.Split(string(content), "\n") @@ -13476,14 +13476,14 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { Expect(err).NotTo(HaveOccurred()) Expect(kernelURL.Host).To(Equal(imageServiceHost)) Expect(kernelURL.Path).To(Equal(imageServicePath + "/boot-artifacts/kernel")) - Expect(kernelURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.OpenshiftVersion)) + Expect(kernelURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.Version)) Expect(kernelURL.Query().Get("arch")).To(Equal(*common.TestDefaultConfig.OsImage.CPUArchitecture)) rootfsURL, err := url.Parse(match[2]) Expect(err).NotTo(HaveOccurred()) Expect(rootfsURL.Host).To(Equal(imageServiceHost)) Expect(rootfsURL.Path).To(Equal(imageServicePath + "/boot-artifacts/rootfs")) - Expect(rootfsURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.OpenshiftVersion)) + Expect(rootfsURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.Version)) Expect(rootfsURL.Query().Get("arch")).To(Equal(*common.TestDefaultConfig.OsImage.CPUArchitecture)) Expect(match[3]).To(Equal(`random.trust_cpu=on rd.luks.options=discard ignition.firstboot ignition.platform.id=metal console=tty1 console=ttyS1,115200n8 coreos.inst.persistent-kargs="console=tty1 console=ttyS1,115200n8"`)) @@ -13498,7 +13498,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { Expect(initrdURL.Scheme).To(Equal("http")) Expect(initrdURL.Host).To(Equal(imageServiceHost)) Expect(initrdURL.Path).To(Equal(fmt.Sprintf("%s/images/%s/pxe-initrd", imageServicePath, infraEnvID))) - Expect(initrdURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.OpenshiftVersion)) + Expect(initrdURL.Query().Get("version")).To(Equal(*common.TestDefaultConfig.OsImage.Version)) Expect(initrdURL.Query().Get("arch")).To(Equal(*common.TestDefaultConfig.OsImage.CPUArchitecture)) Expect(lines[3]).To(Equal("boot")) @@ -13506,7 +13506,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { It("fails to return ipxe-script when openshift version is nil", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(&models.OsImage{}, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(&models.OsImage{}, nil).Times(1) params := installer.V2DownloadInfraEnvFilesParams{InfraEnvID: infraEnvID, FileName: "ipxe-script"} response := bm.V2DownloadInfraEnvFiles(ctx, params) verifyApiError(response, http.StatusInternalServerError) @@ -13514,7 +13514,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { It("fails to return ipxe-script when openshift version can't be found", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(nil, fmt.Errorf("some error")).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(nil, fmt.Errorf("some error")).Times(1) params := installer.V2DownloadInfraEnvFilesParams{InfraEnvID: infraEnvID, FileName: "ipxe-script"} response := bm.V2DownloadInfraEnvFiles(ctx, params) verifyApiError(response, http.StatusBadRequest) @@ -13556,7 +13556,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { } Expect(db.Create(&host).Error).ToNot(HaveOccurred()) - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) content := getResponseData("ipxe-script", true, nil, "", infraEnvID) initrdRegex := regexp.MustCompile(`^initrd --name initrd (.+)`) match := initrdRegex.FindStringSubmatch(strings.Split(string(content), "\n")[1]) @@ -13666,7 +13666,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { It("signs the initrd ipxe-script url correctly", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) content := getResponseData("ipxe-script", false, nil, "", infraEnvID) initrdRegex := regexp.MustCompile(`^initrd --name initrd (.+)`) match := initrdRegex.FindStringSubmatch(strings.Split(string(content), "\n")[1]) @@ -13690,7 +13690,7 @@ var _ = Describe("V2DownloadInfraEnvFiles", func() { It("signs the initrd ipxe-script url correctly", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) content := getResponseData("ipxe-script", false, nil, "", infraEnvID) initrdRegex := regexp.MustCompile(`^initrd --name initrd (.+)`) match := initrdRegex.FindStringSubmatch(strings.Split(string(content), "\n")[1]) @@ -13775,7 +13775,7 @@ var _ = Describe("UpdateInfraEnv - Ignition", func() { } mockUsageReports() - mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).AnyTimes() mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -13850,7 +13850,7 @@ var _ = Describe("UpdateInfraEnv - Ignition", func() { InfraEnvUpdateParams: &models.InfraEnvUpdateParams{IgnitionConfigOverride: override}, } - mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).AnyTimes() mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -13884,7 +13884,7 @@ var _ = Describe("UpdateInfraEnv - Ignition", func() { InfraEnvUpdateParams: &models.InfraEnvUpdateParams{IgnitionConfigOverride: override}, } - mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(discovery_ignition_3_1, nil).Times(1) mockEvents.EXPECT().SendInfraEnvEvent(gomock.Any(), eventstest.NewEventMatcher( eventstest.WithNameMatcher(eventgen.ImageInfoUpdatedEventName), @@ -13905,7 +13905,7 @@ var _ = Describe("UpdateInfraEnv - Ignition", func() { largeDiscoveryIgnition := fmt.Sprintf(`{"ignition": {"version": "3.1.0"}, "storage": {"files": [{"path": "/tmp/example", "contents": {"source": "data:text/plain;base64,%s"}}]}}`, content) override := `{"ignition": {"version": "3.1.0"}, "storage": {"files": [{"path": "/tmp/example", "contents": {"source": "data:text/plain;base64,aGVscGltdHJhcHBlZGluYXN3YWdnZXJzcGVj"}}]}}` - mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest("", gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).AnyTimes() mockIgnitionBuilder.EXPECT().FormatDiscoveryIgnitionFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(largeDiscoveryIgnition, nil).AnyTimes() mockUsage.EXPECT().Add(gomock.Any(), usage.IgnitionConfigOverrideUsage, gomock.Any()).Times(1) mockUsage.EXPECT().Save(gomock.Any(), gomock.Any(), gomock.Any()).Times(1) @@ -14760,7 +14760,7 @@ var _ = Describe("RegisterCluster", func() { mockVersions.EXPECT().GetReleaseImage(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(releaseImage, nil).Times(1) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) mockOperatorManager.EXPECT().GetSupportedOperatorsByType(models.OperatorTypeBuiltin).Return([]*models.MonitoredOperator{&common.TestDefaultConfig.MonitoredOperator}).Times(1) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ @@ -15066,7 +15066,7 @@ var _ = Describe("RegisterCluster", func() { }, nil).Times(1) mockOperatorManager.EXPECT().GetSupportedOperatorsByType(models.OperatorTypeBuiltin).Return([]*models.MonitoredOperator{&common.TestDefaultConfig.MonitoredOperator}).Times(1) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) params := getClusterCreateParams(nil) params.Platform = &models.Platform{ @@ -16678,7 +16678,7 @@ var _ = Describe("RegisterCluster", func() { Return(errors.New("error")).Times(1) mockOperatorManager.EXPECT().GetSupportedOperatorsByType(models.OperatorTypeBuiltin).Return([]*models.MonitoredOperator{}).Times(1) mockVersions.EXPECT().GetReleaseImage(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.ReleaseImage, nil).Times(1) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) clusterParams := getDefaultClusterCreateParams() clusterParams.PullSecret = swag.String("") @@ -21741,7 +21741,7 @@ var _ = Describe("GetInfraEnvDownloadURL", func() { }) getNewURL := func() *models.PresignedURL { - mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, gomock.Any(), "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) params := installer.GetInfraEnvDownloadURLParams{InfraEnvID: infraEnvID} resp := bm.GetInfraEnvDownloadURL(ctx, params) Expect(resp).To(BeAssignableToTypeOf(&installer.GetInfraEnvDownloadURLOK{})) @@ -21762,7 +21762,7 @@ var _ = Describe("GetInfraEnvDownloadURL", func() { fmt.Sprintf("%s/byid/%s/%s/%s/full.iso", imageServicePath, infraEnvID, - *common.TestDefaultConfig.OsImage.OpenshiftVersion, + *common.TestDefaultConfig.OsImage.Version, *common.TestDefaultConfig.OsImage.CPUArchitecture))) }) }) @@ -21796,7 +21796,7 @@ var _ = Describe("GetInfraEnvDownloadURL", func() { pathList := strings.Split(u.Path, "/") Expect(pathList).To(HaveLen(8)) Expect(pathList[3]).To(Equal("byapikey")) - Expect(pathList[5]).To(Equal(common.TestDefaultConfig.OpenShiftVersion)) + Expect(pathList[5]).To(Equal(*common.TestDefaultConfig.OsImage.Version)) tok := pathList[4] _, err = bm.authHandler.AuthURLAuth(tok) @@ -21822,7 +21822,7 @@ var _ = Describe("GetInfraEnvDownloadURL", func() { pathList := strings.Split(u.Path, "/") Expect(pathList).To(HaveLen(8)) Expect(pathList[3]).To(Equal("bytoken")) - Expect(pathList[5]).To(Equal(common.TestDefaultConfig.OpenShiftVersion)) + Expect(pathList[5]).To(Equal(*common.TestDefaultConfig.OsImage.Version)) tok := pathList[4] _, err = bm.authHandler.AuthImageAuth(tok) diff --git a/internal/bminventory/inventory_v2_handlers.go b/internal/bminventory/inventory_v2_handlers.go index 162088665105..e75a0654dab0 100644 --- a/internal/bminventory/inventory_v2_handlers.go +++ b/internal/bminventory/inventory_v2_handlers.go @@ -886,15 +886,16 @@ func (b *bareMetalInventory) GetInfraEnvDownloadURL(ctx context.Context, params return common.GenerateErrorResponder(common.NewApiError(http.StatusBadRequest, errors.New("image service is disabled"))) } - osImage, err := b.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture) + osImage, err := b.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, infraEnv.OsStream) if err != nil { return common.GenerateErrorResponder(common.NewApiError(http.StatusBadRequest, err)) } - if osImage.OpenshiftVersion == nil { - return common.GenerateErrorResponder(errors.Errorf("OS image entry '%+v' missing OpenshiftVersion field", osImage)) + version, err := imageservice.OsImageVersion(osImage) + if err != nil { + return common.GenerateErrorResponder(err) } - newURL, expiresAt, err := b.generateShortImageDownloadURL(infraEnv.ID.String(), string(*infraEnv.Type), *osImage.OpenshiftVersion, infraEnv.CPUArchitecture, infraEnv.ImageTokenKey) + newURL, expiresAt, err := b.generateShortImageDownloadURL(infraEnv.ID.String(), string(*infraEnv.Type), version, infraEnv.CPUArchitecture, infraEnv.ImageTokenKey) if err != nil { return common.GenerateErrorResponder(err) } @@ -1074,13 +1075,10 @@ func kernelArgsAppendStr(infraEnv *common.InfraEnv) (string, error) { } func (b *bareMetalInventory) bootIPXEScript(ctx context.Context, infraEnv *common.InfraEnv) (string, error) { - osImage, err := b.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture) + osImage, err := b.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, infraEnv.OsStream) if err != nil { return "", common.NewApiError(http.StatusBadRequest, err) } - if osImage.OpenshiftVersion == nil { - return "", errors.Errorf("OS image entry '%+v' missing OpenshiftVersion field", osImage) - } bootArtifactURLs, err := imageservice.GetBootArtifactURLs(b.ImageServiceBaseURL, infraEnv.ID.String(), osImage, b.insecureIPXEURLs) if err != nil { diff --git a/internal/controller/controllers/agentserviceconfig_controller.go b/internal/controller/controllers/agentserviceconfig_controller.go index af865f85b758..a4484f3ad586 100644 --- a/internal/controller/controllers/agentserviceconfig_controller.go +++ b/internal/controller/controllers/agentserviceconfig_controller.go @@ -2363,6 +2363,12 @@ func getOSImages(log logrus.FieldLogger, spec *aiv1beta1.AgentServiceConfigSpec, Version: &spec.OSImages[i].Version, CPUArchitecture: &spec.OSImages[i].CPUArchitecture, } + if spec.OSImages[i].OSStream != "" { + osImage.OsStream = &spec.OSImages[i].OSStream + } + if spec.OSImages[i].DefaultOSStream != nil { + osImage.DefaultOsStream = spec.OSImages[i].DefaultOSStream + } osImages = append(osImages, &osImage) } diff --git a/internal/controller/controllers/clusterdeployments_controller.go b/internal/controller/controllers/clusterdeployments_controller.go index 55497030869a..dde27e3917b3 100644 --- a/internal/controller/controllers/clusterdeployments_controller.go +++ b/internal/controller/controllers/clusterdeployments_controller.go @@ -1116,6 +1116,7 @@ func (r *ClusterDeploymentsReconciler) updateIfNeeded( // Trim key before comparing as done in RegisterClusterInternal sshPublicKey := strings.TrimSpace(clusterInstall.Spec.SSHPublicKey) updateString(sshPublicKey, cluster.SSHPublicKey, ¶ms.SSHPublicKey) + updateString(clusterInstall.Spec.OSStream, cluster.OsStream, ¶ms.OsStream) // Update ignition endpoint if needed shouldUpdate, err := r.updateIgnitionInUpdateParams(ctx, log, clusterInstall, cluster, params) @@ -1549,6 +1550,7 @@ func CreateClusterParams(clusterDeployment *hivev1.ClusterDeployment, clusterIns SchedulableMasters: swag.Bool(clusterInstall.Spec.MastersSchedulable), ControlPlaneCount: swag.Int64(int64(clusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents)), OcpReleaseImage: *releaseImage.URL, + OsStream: clusterInstall.Spec.OSStream, } if len(clusterInstall.Spec.Networking.ClusterNetwork) > 0 { diff --git a/internal/controller/controllers/infraenv_controller.go b/internal/controller/controllers/infraenv_controller.go index 55069f6ba7fb..bc6dfd75998b 100644 --- a/internal/controller/controllers/infraenv_controller.go +++ b/internal/controller/controllers/infraenv_controller.go @@ -200,6 +200,9 @@ func (r *InfraEnvReconciler) updateInfraEnv(ctx context.Context, log logrus.Fiel if infraEnv.Spec.OSImageVersion != "" && infraEnv.Spec.OSImageVersion != internalInfraEnv.OpenshiftVersion { updateParams.InfraEnvUpdateParams.OpenshiftVersion = &infraEnv.Spec.OSImageVersion } + if infraEnv.Spec.OSStream != "" && infraEnv.Spec.OSStream != internalInfraEnv.OsStream { + updateParams.InfraEnvUpdateParams.OsStream = &infraEnv.Spec.OSStream + } pullSecretKey := getPullSecretKey(infraEnv.Namespace, infraEnv.Spec.PullSecretRef) pullSecret, err := r.PullSecretHandler.GetValidPullSecret(ctx, pullSecretKey) @@ -448,7 +451,7 @@ func (r *InfraEnvReconciler) reconcileInfraEnv(ctx context.Context, log logrus.F return r.updateInfraEnvStatus(ctx, log, infraEnv, cluster) } -func CreateInfraEnvParams(infraEnv *aiv1beta1.InfraEnv, imageType models.ImageType, pullSecret string, clusterID *strfmt.UUID, openshiftVersion string) installer.RegisterInfraEnvParams { +func CreateInfraEnvParams(infraEnv *aiv1beta1.InfraEnv, imageType models.ImageType, pullSecret string, clusterID *strfmt.UUID, openshiftVersion, osStream string) installer.RegisterInfraEnvParams { createParams := installer.RegisterInfraEnvParams{ InfraenvCreateParams: &models.InfraEnvCreateParams{ Name: &infraEnv.Name, @@ -459,6 +462,7 @@ func CreateInfraEnvParams(infraEnv *aiv1beta1.InfraEnv, imageType models.ImageTy CPUArchitecture: infraEnv.Spec.CpuArchitecture, ClusterID: clusterID, OpenshiftVersion: openshiftVersion, + OsStream: osStream, AdditionalTrustBundle: infraEnv.Spec.AdditionalTrustBundle, NetworkDiscoveryDelaySeconds: infraEnv.Spec.NetworkDiscoveryDelaySeconds, }, @@ -487,7 +491,7 @@ func CreateInfraEnvParams(infraEnv *aiv1beta1.InfraEnv, imageType models.ImageTy // Priority is given to the OSImageVersion specified in the InfraEnv // If there's a cluster reference, return cluster's OpenshiftVersion // If OsImageVersion is specified, return value or fallback to latest if missing from ASC -func (r *InfraEnvReconciler) getOSImageVersion(log logrus.FieldLogger, infraEnv *aiv1beta1.InfraEnv, cluster *common.Cluster) (string, error) { +func (r *InfraEnvReconciler) getOSImageVersion(log logrus.FieldLogger, infraEnv *aiv1beta1.InfraEnv, cluster *common.Cluster, osStream string) (string, error) { osImageVersion := infraEnv.Spec.OSImageVersion if cluster != nil && osImageVersion == "" { @@ -499,7 +503,7 @@ func (r *InfraEnvReconciler) getOSImageVersion(log logrus.FieldLogger, infraEnv return "", nil } - if _, err := r.OsImages.GetOsImage(osImageVersion, infraEnv.Spec.CpuArchitecture); err != nil { + if _, err := r.OsImages.GetOsImage(osImageVersion, infraEnv.Spec.CpuArchitecture, osStream); err != nil { msg := "Specified OSImageVersion is missing from AgentServiceConfig" log.WithError(err).Error(msg) return "", common.NewApiError(http.StatusNotFound, errors.New(msg)) @@ -507,10 +511,22 @@ func (r *InfraEnvReconciler) getOSImageVersion(log logrus.FieldLogger, infraEnv return osImageVersion, nil } +// getOSStream returns the OS stream for the InfraEnv. Priority is given to Spec.OSStream; +// if empty and a cluster is referenced, the cluster's OsStream is used. +func (r *InfraEnvReconciler) getOSStream(infraEnv *aiv1beta1.InfraEnv, cluster *common.Cluster) string { + osStream := infraEnv.Spec.OSStream + if cluster != nil && osStream == "" { + osStream = cluster.OsStream + } + + return osStream +} + func (r *InfraEnvReconciler) createInfraEnv(ctx context.Context, log logrus.FieldLogger, key *types.NamespacedName, infraEnv *aiv1beta1.InfraEnv, cluster *common.Cluster) (*common.InfraEnv, error) { - osImageVersion, err := r.getOSImageVersion(log, infraEnv, cluster) + osStream := r.getOSStream(infraEnv, cluster) + osImageVersion, err := r.getOSImageVersion(log, infraEnv, cluster, osStream) if err != nil { log.WithError(err).Error("failed to get OS image version") return nil, err @@ -527,7 +543,7 @@ func (r *InfraEnvReconciler) createInfraEnv(ctx context.Context, log logrus.Fiel clusterID = cluster.ID } imageType := infraenv.GetInfraEnvIsoImageType(log, infraEnv.Spec.CpuArchitecture, infraEnv.Spec.ImageType, r.Config.ImageType) - createParams := CreateInfraEnvParams(infraEnv, imageType, pullSecret, clusterID, osImageVersion) + createParams := CreateInfraEnvParams(infraEnv, imageType, pullSecret, clusterID, osImageVersion, osStream) staticNetworkConfig, err := r.processNMStateConfig(ctx, log, infraEnv) if err != nil { @@ -699,7 +715,7 @@ func (r *InfraEnvReconciler) setBootArtifactURLs(log logrus.FieldLogger, infraEn var bootArtifactURLs *imageservice.BootArtifactURLs var err error var osImage *models.OsImage - if osImage, err = r.OsImages.GetOsImageOrLatest(internalInfraEnv.OpenshiftVersion, internalInfraEnv.CPUArchitecture); err != nil { + if osImage, err = r.OsImages.GetOsImageOrLatest(internalInfraEnv.OpenshiftVersion, internalInfraEnv.CPUArchitecture, internalInfraEnv.OsStream); err != nil { return err } if bootArtifactURLs, err = imageservice.GetBootArtifactURLs(r.ImageServiceBaseURL, internalInfraEnv.ID.String(), osImage, r.InsecureIPXEURLs); err != nil { diff --git a/internal/controller/controllers/infraenv_controller_test.go b/internal/controller/controllers/infraenv_controller_test.go index 584c6341bf8c..6808e42e925a 100644 --- a/internal/controller/controllers/infraenv_controller_test.go +++ b/internal/controller/controllers/infraenv_controller_test.go @@ -116,8 +116,8 @@ var _ = Describe("infraEnv reconcile", func() { eventURL = fmt.Sprintf("%s/api/assisted-install/v2/events?infra_env_id=%s", ir.ServiceBaseURL, sId) Expect(c.Create(ctx, pullSecret)).To(BeNil()) mockOSImages.EXPECT().GetOpenshiftVersions().Return([]string{"4.8"}).AnyTimes() - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() - mockOSImages.EXPECT().GetLatestOsImage(infraEnvArch).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() + mockOSImages.EXPECT().GetLatestOsImage(infraEnvArch, "").Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() }) AfterEach(func() { @@ -1152,7 +1152,7 @@ var _ = Describe("infraEnv reconcile", func() { Do(func(ctx context.Context, kubeKey *types.NamespacedName, mirrorRegistryConfiguration *common.MirrorRegistryConfiguration, params installer.RegisterInfraEnvParams) { Expect(params.InfraenvCreateParams.OpenshiftVersion).To(Equal(osImageVersion)) }).Return(backendInfraEnv, nil) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(osImageVersion)}, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(osImageVersion)}, nil).AnyTimes() result, err := ir.Reconcile(ctx, newInfraEnvRequest(infraEnvImage)) Expect(err).To(BeNil()) @@ -1171,7 +1171,7 @@ var _ = Describe("infraEnv reconcile", func() { }) Expect(c.Create(ctx, infraEnvImage)).To(BeNil()) mockInstallerInternal.EXPECT().GetInfraEnvByKubeKey(gomock.Any()).Return(nil, gorm.ErrRecordNotFound) - mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any()).Return(nil, gorm.ErrRecordNotFound).AnyTimes() + mockOSImages.EXPECT().GetOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, gorm.ErrRecordNotFound).AnyTimes() result, err := ir.Reconcile(ctx, newInfraEnvRequest(infraEnvImage)) Expect(err).To(BeNil()) @@ -1315,7 +1315,7 @@ var _ = Describe("infraEnv reconcile", func() { }, AdditionalTrustBundle: "AdditionalTrustBundle", }) - params := CreateInfraEnvParams(infraEnvImage, models.ImageType(imageType), pullSecretString, cluster.ID, cluster.OpenshiftVersion) + params := CreateInfraEnvParams(infraEnvImage, models.ImageType(imageType), pullSecretString, cluster.ID, cluster.OpenshiftVersion, "") Expect(params).ToNot(BeNil()) Expect(params.InfraenvCreateParams.ClusterID).To(Equal(cluster.ID)) @@ -1509,8 +1509,8 @@ var _ = Describe("infraEnv reconcile with image service disabled", func() { eventURL = fmt.Sprintf("%s/api/assisted-install/v2/events?infra_env_id=%s", ir.ServiceBaseURL, sId) Expect(c.Create(ctx, pullSecret)).To(BeNil()) mockOSImages.EXPECT().GetOpenshiftVersions().Return([]string{"4.8"}).AnyTimes() - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() - mockOSImages.EXPECT().GetLatestOsImage(infraEnvArch).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() + mockOSImages.EXPECT().GetLatestOsImage(infraEnvArch, "").Return(&models.OsImage{CPUArchitecture: swag.String(infraEnvArch), OpenshiftVersion: swag.String(ocpVersion)}, nil).AnyTimes() }) AfterEach(func() { diff --git a/internal/host/hostcommands/download_boot_artifacts_cmd.go b/internal/host/hostcommands/download_boot_artifacts_cmd.go index b5bb8bd23931..f0ed7128ae7c 100644 --- a/internal/host/hostcommands/download_boot_artifacts_cmd.go +++ b/internal/host/hostcommands/download_boot_artifacts_cmd.go @@ -12,7 +12,6 @@ import ( "github.com/openshift/assisted-service/internal/versions" "github.com/openshift/assisted-service/models" "github.com/openshift/assisted-service/pkg/auth" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "gorm.io/gorm" ) @@ -47,14 +46,11 @@ func (c *downloadBootArtifactsCmd) GetSteps(ctx context.Context, host *models.Ho return nil, err } - osImage, err := c.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture) + osImage, err := c.osImages.GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, infraEnv.OsStream) if err != nil { return nil, err } - if osImage.OpenshiftVersion == nil { - return nil, errors.Errorf("OS image entry '%+v' missing OpenshiftVersion field", osImage) - } bootArtifactURLs, err := imageservice.GetBootArtifactURLs(c.imageServiceBaseURL, infraEnv.ID.String(), osImage, false) if err != nil { return nil, fmt.Errorf("failed to generate urls for DownloadBootArtifactsRequest: %w", err) diff --git a/internal/host/hostcommands/download_boot_artifacts_cmd_test.go b/internal/host/hostcommands/download_boot_artifacts_cmd_test.go index bae7f84f3c46..237bfd70e387 100644 --- a/internal/host/hostcommands/download_boot_artifacts_cmd_test.go +++ b/internal/host/hostcommands/download_boot_artifacts_cmd_test.go @@ -60,11 +60,12 @@ var _ = Describe("downloadBootArtifactsCmd.GetSteps", func() { infraEnv.OpenshiftVersion = *common.TestDefaultConfig.OsImage.OpenshiftVersion Expect(db.Create(infraEnv).Error).To(BeNil()) downloadCmd = NewDownloadBootArtifactsCmd(common.GetTestLog(), imgSvcURL, auth.TypeNone, mockOSImages, db, time.Duration(9000), hostFSMountDir) - mockOSImages.EXPECT().GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, "").Return(common.TestDefaultConfig.OsImage, nil).Times(1) - initrdUrl := fmt.Sprintf("%s/images/%s/pxe-initrd?arch=%s&version=%s", imgSvcURL, infraEnvID, infraEnv.CPUArchitecture, infraEnv.OpenshiftVersion) - rootfsUrl := fmt.Sprintf("%s/boot-artifacts/rootfs?arch=%s&version=%s", imgSvcURL, infraEnv.CPUArchitecture, infraEnv.OpenshiftVersion) - kernelUrl := fmt.Sprintf("%s/boot-artifacts/kernel?arch=%s&version=%s", imgSvcURL, infraEnv.CPUArchitecture, infraEnv.OpenshiftVersion) + rhcosVersion := *common.TestDefaultConfig.OsImage.Version + initrdUrl := fmt.Sprintf("%s/images/%s/pxe-initrd?arch=%s&version=%s", imgSvcURL, infraEnvID, infraEnv.CPUArchitecture, rhcosVersion) + rootfsUrl := fmt.Sprintf("%s/boot-artifacts/rootfs?arch=%s&version=%s", imgSvcURL, infraEnv.CPUArchitecture, rhcosVersion) + kernelUrl := fmt.Sprintf("%s/boot-artifacts/kernel?arch=%s&version=%s", imgSvcURL, infraEnv.CPUArchitecture, rhcosVersion) stepReply, stepErr := downloadCmd.GetSteps(ctx, &host) Expect(stepErr).To(BeNil()) @@ -94,7 +95,7 @@ var _ = Describe("downloadBootArtifactsCmd.GetSteps", func() { Expect(db.Create(infraEnv).Error).To(BeNil()) downloadCmd = NewDownloadBootArtifactsCmd(common.GetTestLog(), imgSvcURL, auth.TypeNone, mockOSImages, db, time.Duration(9000), hostFSMountDir) versionsErr := errors.Errorf("The requested OS image for version (%s) and CPU architecture (%s) isn't specified in OS images list", infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture) - mockOSImages.EXPECT().GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture).Return(nil, versionsErr).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, "").Return(nil, versionsErr).Times(1) _, stepErr := downloadCmd.GetSteps(ctx, &host) Expect(stepErr).ToNot(BeNil()) }) @@ -105,7 +106,7 @@ var _ = Describe("downloadBootArtifactsCmd.GetSteps", func() { Expect(db.Create(infraEnv).Error).To(BeNil()) downloadCmd = NewDownloadBootArtifactsCmd(common.GetTestLog(), imgSvcURL, auth.TypeNone, mockOSImages, db, time.Duration(9000), hostFSMountDir) versionsErr := errors.Errorf("No OS images are available") - mockOSImages.EXPECT().GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture).Return(nil, versionsErr).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(infraEnv.OpenshiftVersion, infraEnv.CPUArchitecture, "").Return(nil, versionsErr).Times(1) _, stepErr := downloadCmd.GetSteps(ctx, &host) Expect(stepErr).ToNot(BeNil()) }) @@ -115,7 +116,7 @@ var _ = Describe("downloadBootArtifactsCmd.GetSteps", func() { Expect(db.Create(infraEnv).Error).To(BeNil()) downloadCmd = NewDownloadBootArtifactsCmd(common.GetTestLog(), imgSvcURL, auth.TypeNone, mockOSImages, db, time.Duration(9000), hostFSMountDir) versionsErr := errors.Errorf("No OS images are available") - mockOSImages.EXPECT().GetOsImageOrLatest("", "").Return(nil, versionsErr).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest("", "", "").Return(nil, versionsErr).Times(1) _, stepErr := downloadCmd.GetSteps(ctx, &host) Expect(stepErr).ToNot(BeNil()) }) diff --git a/internal/host/hostcommands/instruction_manager_test.go b/internal/host/hostcommands/instruction_manager_test.go index 9c883c70b85c..78a4bd0065b4 100644 --- a/internal/host/hostcommands/instruction_manager_test.go +++ b/internal/host/hostcommands/instruction_manager_test.go @@ -320,7 +320,7 @@ var _ = Describe("instruction_manager", func() { checkStep(models.HostStatusUnbindingPendingUserAction, nil) }) It("reclaiming", func() { - mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) + mockOSImages.EXPECT().GetOsImageOrLatest(gomock.Any(), gomock.Any(), gomock.Any()).Return(common.TestDefaultConfig.OsImage, nil).Times(1) checkStep(models.HostStatusReclaiming, []models.StepType{ models.StepTypeDownloadBootArtifacts, }) diff --git a/internal/imageservice/url_test.go b/internal/imageservice/url_test.go index cda70d145b4a..f0493f4b9c18 100644 --- a/internal/imageservice/url_test.go +++ b/internal/imageservice/url_test.go @@ -5,6 +5,7 @@ import ( "net/url" "testing" + "github.com/go-openapi/swag" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/openshift/assisted-service/models" @@ -172,3 +173,22 @@ func checkURL(u, scheme, host, path, version, arch string) { Expect(parsed.Query().Get("version")).To(Equal(version)) Expect(parsed.Query().Get("arch")).To(Equal(arch)) } + +var _ = Describe("OsImageVersion", func() { + It("prefers RHCOS version", func() { + v, err := OsImageVersion(&models.OsImage{ + OpenshiftVersion: swag.String("4.22"), + Version: swag.String("9.6.20260101-0"), + }) + Expect(err).ShouldNot(HaveOccurred()) + Expect(v).To(Equal("9.6.20260101-0")) + }) + + It("falls back to openshift version", func() { + v, err := OsImageVersion(&models.OsImage{ + OpenshiftVersion: swag.String("4.22"), + }) + Expect(err).ShouldNot(HaveOccurred()) + Expect(v).To(Equal("4.22")) + }) +}) diff --git a/internal/imageservice/urls.go b/internal/imageservice/urls.go index 73b67c71d832..ef39722c95cf 100644 --- a/internal/imageservice/urls.go +++ b/internal/imageservice/urls.go @@ -5,6 +5,7 @@ import ( "net/url" "path" + "github.com/go-openapi/swag" "github.com/openshift/assisted-service/models" "github.com/pkg/errors" ) @@ -76,8 +77,27 @@ func ShortImageURL(baseURL string, prefix ShortImageURLPrefix, token, version, a return buildURL(baseURL, path, false, map[string]string{}) } +// OsImageVersion returns the version string to use in image-service URLs. +// Prefers the RHCOS build ID so multiple OS streams for the same OpenShift +// version can be disambiguated. +func OsImageVersion(osImage *models.OsImage) (string, error) { + if osImage == nil { + return "", errors.New("OS image is nil") + } + if v := swag.StringValue(osImage.Version); v != "" { + return v, nil + } + if v := swag.StringValue(osImage.OpenshiftVersion); v != "" { + return v, nil + } + return "", errors.Errorf("OS image entry '%+v' missing Version and OpenshiftVersion fields", osImage) +} + func GetBootArtifactURLs(baseURL, imageID string, osImage *models.OsImage, insecure bool) (*BootArtifactURLs, error) { - version := *osImage.OpenshiftVersion + version, err := OsImageVersion(osImage) + if err != nil { + return nil, err + } arch := *osImage.CPUArchitecture kernelUrl, err := KernelURL(baseURL, version, arch, insecure) if err != nil { diff --git a/internal/installcfg/builder/builder.go b/internal/installcfg/builder/builder.go index 098855e9b5bc..82c10278f4bb 100644 --- a/internal/installcfg/builder/builder.go +++ b/internal/installcfg/builder/builder.go @@ -109,8 +109,9 @@ func (i *installConfigBuilder) getBasicInstallConfig(cluster *common.Cluster) (* Name: string(models.HostRoleMaster), Replicas: i.countHostsByRole(cluster, models.HostRoleMaster), }, - PullSecret: cluster.PullSecret, - SSHKey: cluster.SSHPublicKey, + PullSecret: cluster.PullSecret, + SSHKey: cluster.SSHPublicKey, + OSImageStream: cluster.OsStream, } // For "None" network type, don't set networkType in install-config (user provides CNI manifests) diff --git a/internal/installcfg/builder/builder_test.go b/internal/installcfg/builder/builder_test.go index 289fdd5f438f..516ddc2b2d2b 100644 --- a/internal/installcfg/builder/builder_test.go +++ b/internal/installcfg/builder/builder_test.go @@ -142,6 +142,19 @@ aEA8gNEmV+rb7h1v0r3EwDQYJKoZIhvcNAQELBQAwYTELMAkGA1UEBhMCaXMxCzAJBgNVBAgMAmRk Expect(result.Arbiter).Should(BeNil()) Expect(result.FeatureSet).To(Equal(configv1.Default)) Expect(result.FeatureGates).To(HaveLen(0)) + Expect(result.OSImageStream).To(BeEmpty()) + }) + + It("sets osImageStream from cluster OsStream", func() { + cluster.OsStream = "rhel-10" + + var result installcfg.InstallerConfigBaremetal + mockMirrorRegistriesConfigBuilder.EXPECT().IsMirrorRegistriesConfigured().Return(false).Times(2) + data, err := installConfig.GetInstallConfig(&cluster, clusterInfraenvs, "") + Expect(err).ShouldNot(HaveOccurred()) + err = json.Unmarshal(data, &result) + Expect(err).ShouldNot(HaveOccurred()) + Expect(result.OSImageStream).To(Equal("rhel-10")) }) It("create_configuration_with_all_hosts - TNA cluster TechPreview", func() { @@ -256,9 +269,9 @@ aEA8gNEmV+rb7h1v0r3EwDQYJKoZIhvcNAQELBQAwYTELMAkGA1UEBhMCaXMxCzAJBgNVBAgMAmRk Username: swag.String("username"), } fencingCredentials2 := models.FencingCredentialsParams{ - Address: swag.String("https://address2.example.com"), - Password: swag.String("password"), - Username: swag.String("username"), + Address: swag.String("https://address2.example.com"), + Password: swag.String("password"), + Username: swag.String("username"), } fencingCredentialsHost1String, err := json.Marshal(fencingCredentials1) Expect(err).ShouldNot(HaveOccurred()) diff --git a/internal/versions/api.go b/internal/versions/api.go index 6108205a3696..b336a5fa44ea 100644 --- a/internal/versions/api.go +++ b/internal/versions/api.go @@ -257,7 +257,7 @@ func (h *apiHandler) V2ListSupportedOpenshiftVersions(ctx context.Context, param // In order to mark a specific version and architecture as supported we do not // only need to have an available release image, but we need RHCOS image as well. - if _, err := h.osImages.GetOsImage(displayName, arch); err != nil { + if _, err := h.osImages.GetOsImage(displayName, arch, ""); err != nil { h.log.Debugf("Marking architecture %s for version %s as not available because no matching OS image found", arch, displayName) continue } diff --git a/internal/versions/mock_osimages.go b/internal/versions/mock_osimages.go index 8940f1ef23be..9b9b31b6ce87 100644 --- a/internal/versions/mock_osimages.go +++ b/internal/versions/mock_osimages.go @@ -55,18 +55,18 @@ func (mr *MockOSImagesMockRecorder) GetCPUArchitectures(openshiftVersion any) *g } // GetLatestOsImage mocks base method. -func (m *MockOSImages) GetLatestOsImage(cpuArchitecture string) (*models.OsImage, error) { +func (m *MockOSImages) GetLatestOsImage(cpuArchitecture, osStream string) (*models.OsImage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLatestOsImage", cpuArchitecture) + ret := m.ctrl.Call(m, "GetLatestOsImage", cpuArchitecture, osStream) ret0, _ := ret[0].(*models.OsImage) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLatestOsImage indicates an expected call of GetLatestOsImage. -func (mr *MockOSImagesMockRecorder) GetLatestOsImage(cpuArchitecture any) *gomock.Call { +func (mr *MockOSImagesMockRecorder) GetLatestOsImage(cpuArchitecture, osStream any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestOsImage", reflect.TypeOf((*MockOSImages)(nil).GetLatestOsImage), cpuArchitecture) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestOsImage", reflect.TypeOf((*MockOSImages)(nil).GetLatestOsImage), cpuArchitecture, osStream) } // GetOpenshiftVersions mocks base method. @@ -84,31 +84,31 @@ func (mr *MockOSImagesMockRecorder) GetOpenshiftVersions() *gomock.Call { } // GetOsImage mocks base method. -func (m *MockOSImages) GetOsImage(openshiftVersion, cpuArchitecture string) (*models.OsImage, error) { +func (m *MockOSImages) GetOsImage(openshiftVersion, cpuArchitecture, osStream string) (*models.OsImage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetOsImage", openshiftVersion, cpuArchitecture) + ret := m.ctrl.Call(m, "GetOsImage", openshiftVersion, cpuArchitecture, osStream) ret0, _ := ret[0].(*models.OsImage) ret1, _ := ret[1].(error) return ret0, ret1 } // GetOsImage indicates an expected call of GetOsImage. -func (mr *MockOSImagesMockRecorder) GetOsImage(openshiftVersion, cpuArchitecture any) *gomock.Call { +func (mr *MockOSImagesMockRecorder) GetOsImage(openshiftVersion, cpuArchitecture, osStream any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOsImage", reflect.TypeOf((*MockOSImages)(nil).GetOsImage), openshiftVersion, cpuArchitecture) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOsImage", reflect.TypeOf((*MockOSImages)(nil).GetOsImage), openshiftVersion, cpuArchitecture, osStream) } // GetOsImageOrLatest mocks base method. -func (m *MockOSImages) GetOsImageOrLatest(version, cpuArch string) (*models.OsImage, error) { +func (m *MockOSImages) GetOsImageOrLatest(version, cpuArch, osStream string) (*models.OsImage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetOsImageOrLatest", version, cpuArch) + ret := m.ctrl.Call(m, "GetOsImageOrLatest", version, cpuArch, osStream) ret0, _ := ret[0].(*models.OsImage) ret1, _ := ret[1].(error) return ret0, ret1 } // GetOsImageOrLatest indicates an expected call of GetOsImageOrLatest. -func (mr *MockOSImagesMockRecorder) GetOsImageOrLatest(version, cpuArch any) *gomock.Call { +func (mr *MockOSImagesMockRecorder) GetOsImageOrLatest(version, cpuArch, osStream any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOsImageOrLatest", reflect.TypeOf((*MockOSImages)(nil).GetOsImageOrLatest), version, cpuArch) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOsImageOrLatest", reflect.TypeOf((*MockOSImages)(nil).GetOsImageOrLatest), version, cpuArch, osStream) } diff --git a/internal/versions/osimages.go b/internal/versions/osimages.go index 3cd9322c301d..37bc3bef1de9 100644 --- a/internal/versions/osimages.go +++ b/internal/versions/osimages.go @@ -15,9 +15,9 @@ import ( //go:generate mockgen --build_flags=--mod=mod -package versions -destination mock_osimages.go -self_package github.com/openshift/assisted-service/internal/versions . OSImages type OSImages interface { - GetOsImage(openshiftVersion, cpuArchitecture string) (*models.OsImage, error) - GetLatestOsImage(cpuArchitecture string) (*models.OsImage, error) - GetOsImageOrLatest(version string, cpuArch string) (*models.OsImage, error) + GetOsImage(openshiftVersion, cpuArchitecture, osStream string) (*models.OsImage, error) + GetLatestOsImage(cpuArchitecture, osStream string) (*models.OsImage, error) + GetOsImageOrLatest(version, cpuArch, osStream string) (*models.OsImage, error) GetCPUArchitectures(openshiftVersion string) []string GetOpenshiftVersions() []string } @@ -61,8 +61,8 @@ func validateOSImage(osImage *models.OsImage) error { return nil } -// Returns the OsImage entity -func (images osImageList) GetOsImage(openshiftVersion, cpuArchitecture string) (*models.OsImage, error) { +// Returns the OsImage entity matching the given OpenShift version, CPU architecture and optional OS stream. +func (images osImageList) GetOsImage(openshiftVersion, cpuArchitecture, osStream string) (*models.OsImage, error) { cpuArchitecture = common.NormalizeCPUArchitecture(cpuArchitecture) if cpuArchitecture == "" { @@ -76,62 +76,117 @@ func (images osImageList) GetOsImage(openshiftVersion, cpuArchitecture string) ( return cpuArchitecture == common.DefaultCPUArchitecture } return swag.StringValue(osImage.CPUArchitecture) == cpuArchitecture - }) + }).([]*models.OsImage) if funk.IsEmpty(archImages) { return nil, errors.Errorf("The requested CPU architecture (%s) isn't specified in OS images list", cpuArchitecture) } + candidates := findVersionCandidates(archImages, openshiftVersion) + if len(candidates) == 0 { + return nil, errors.Errorf( + "The requested OS image for version (%s) and CPU architecture (%s) isn't specified in OS images list", + openshiftVersion, cpuArchitecture) + } + + return selectByOsStream(candidates, osStream, openshiftVersion, cpuArchitecture) +} + +func findVersionCandidates(archImages []*models.OsImage, openshiftVersion string) []*models.OsImage { // Search for specified x.y.z openshift version - osImage := funk.Find(archImages, func(osImage *models.OsImage) bool { + exact := funk.Filter(archImages, func(osImage *models.OsImage) bool { return swag.StringValue(osImage.OpenshiftVersion) == openshiftVersion - }) + }).([]*models.OsImage) + if len(exact) > 0 { + return exact + } versionKey, err := common.GetMajorMinorVersion(openshiftVersion) if err != nil { - return nil, err + return nil } - if osImage == nil { - // Fallback to x.y version - osImage = funk.Find(archImages, func(osImage *models.OsImage) bool { - return *osImage.OpenshiftVersion == *versionKey - }) + // Fallback to x.y version + majorMinor := funk.Filter(archImages, func(osImage *models.OsImage) bool { + return *osImage.OpenshiftVersion == *versionKey + }).([]*models.OsImage) + if len(majorMinor) > 0 { + return majorMinor } - if osImage == nil { - // Find latest available patch version by x.y version - osImages := funk.Filter(archImages, func(osImage *models.OsImage) bool { - imageVersionKey, err := common.GetMajorMinorVersion(*osImage.OpenshiftVersion) - if err != nil { - return false - } - return *imageVersionKey == *versionKey - }).([]*models.OsImage) - sort.Slice(osImages, func(i, j int) bool { - v1, _ := version.NewVersion(*osImages[i].OpenshiftVersion) - v2, _ := version.NewVersion(*osImages[j].OpenshiftVersion) - return v1.GreaterThan(v2) + // Find latest available patch version by x.y version + patchMatches := funk.Filter(archImages, func(osImage *models.OsImage) bool { + imageVersionKey, err := common.GetMajorMinorVersion(*osImage.OpenshiftVersion) + if err != nil { + return false + } + return *imageVersionKey == *versionKey + }).([]*models.OsImage) + if len(patchMatches) == 0 { + return nil + } + + sort.Slice(patchMatches, func(i, j int) bool { + v1, _ := version.NewVersion(*patchMatches[i].OpenshiftVersion) + v2, _ := version.NewVersion(*patchMatches[j].OpenshiftVersion) + return v1.GreaterThan(v2) + }) + + latestVersion := *patchMatches[0].OpenshiftVersion + return funk.Filter(patchMatches, func(osImage *models.OsImage) bool { + return *osImage.OpenshiftVersion == latestVersion + }).([]*models.OsImage) +} + +func selectByOsStream(candidates []*models.OsImage, osStream, openshiftVersion, cpuArchitecture string) (*models.OsImage, error) { + if osStream != "" { + match := funk.Find(candidates, func(osImage *models.OsImage) bool { + return swag.StringValue(osImage.OsStream) == osStream }) - if !funk.IsEmpty(osImages) { - osImage = osImages[0] + if match == nil { + return nil, errors.Errorf( + "The requested OS image for version (%s), CPU architecture (%s) and OS stream (%s) isn't specified in OS images list", + openshiftVersion, cpuArchitecture, osStream) } + return match.(*models.OsImage), nil } - if osImage != nil { - return osImage.(*models.OsImage), nil + hasOsStreamMetadata := false + for _, c := range candidates { + if swag.StringValue(c.OsStream) != "" || swag.BoolValue(c.DefaultOsStream) { + hasOsStreamMetadata = true + break + } + } + + // Legacy catalog with no stream metadata: keep first-match behavior + if !hasOsStreamMetadata { + return candidates[0], nil } - return nil, errors.Errorf( - "The requested OS image for version (%s) and CPU architecture (%s) isn't specified in OS images list", - openshiftVersion, cpuArchitecture) + defaults := funk.Filter(candidates, func(osImage *models.OsImage) bool { + return swag.BoolValue(osImage.DefaultOsStream) + }).([]*models.OsImage) + + switch len(defaults) { + case 1: + return defaults[0], nil + case 0: + return nil, errors.Errorf( + "No default OS stream found for version (%s) and CPU architecture (%s)", + openshiftVersion, cpuArchitecture) + default: + return nil, errors.Errorf( + "Multiple default OS streams found for version (%s) and CPU architecture (%s)", + openshiftVersion, cpuArchitecture) + } } -// Returns the latest OSImage entity for a specified CPU architecture -func (images osImageList) GetLatestOsImage(cpuArchitecture string) (*models.OsImage, error) { +// Returns the latest OSImage entity for a specified CPU architecture and optional OS stream +func (images osImageList) GetLatestOsImage(cpuArchitecture, osStream string) (*models.OsImage, error) { var latest *models.OsImage openshiftVersions := images.GetOpenshiftVersions() for _, k := range openshiftVersions { - osImage, err := images.GetOsImage(k, cpuArchitecture) + osImage, err := images.GetOsImage(k, cpuArchitecture, osStream) if err != nil { continue } @@ -151,18 +206,18 @@ func (images osImageList) GetLatestOsImage(cpuArchitecture string) (*models.OsIm return latest, nil } -func (images osImageList) GetOsImageOrLatest(version string, cpuArch string) (*models.OsImage, error) { +func (images osImageList) GetOsImageOrLatest(version, cpuArch, osStream string) (*models.OsImage, error) { var osImage *models.OsImage var err error if version != "" { - osImage, err = images.GetOsImage(version, cpuArch) + osImage, err = images.GetOsImage(version, cpuArch, osStream) if err != nil { - return nil, errors.Wrapf(err, "No OS image for Openshift version %s and architecture %s", version, cpuArch) + return nil, errors.Wrapf(err, "No OS image for Openshift version (%s), CPU architecture (%s) and OS stream (%s)", version, cpuArch, osStream) } } else { - osImage, err = images.GetLatestOsImage(cpuArch) + osImage, err = images.GetLatestOsImage(cpuArch, osStream) if err != nil { - return nil, errors.Wrapf(err, "Failed to get latest OS image for architecture %s", cpuArch) + return nil, errors.Wrapf(err, "Failed to get latest OS image for CPU architecture (%s) and OS stream (%s)", cpuArch, osStream) } } return osImage, nil diff --git a/internal/versions/osimages_test.go b/internal/versions/osimages_test.go index c6bcd7f29c74..502c613e03d5 100644 --- a/internal/versions/osimages_test.go +++ b/internal/versions/osimages_test.go @@ -90,39 +90,39 @@ var _ = Describe("GetOsImage", func() { }) It("fails for an unsupported version", func() { - image, err := images.GetOsImage("unsupported", common.TestDefaultConfig.CPUArchitecture) + image, err := images.GetOsImage("unsupported", common.TestDefaultConfig.CPUArchitecture, "") Expect(err).Should(HaveOccurred()) Expect(image).Should(BeNil()) }) It("fails for an unsupported cpuArchitecture", func() { - image, err := images.GetOsImage(common.TestDefaultConfig.OpenShiftVersion, "unsupported") + image, err := images.GetOsImage(common.TestDefaultConfig.OpenShiftVersion, "unsupported", "") Expect(err).Should(HaveOccurred()) Expect(image).Should(BeNil()) Expect(err.Error()).To(ContainSubstring("isn't specified in OS images list")) }) It("empty architecture fallback to default", func() { - image, err := images.GetOsImage("4.9", "") + image, err := images.GetOsImage("4.9", "", "") Expect(err).ShouldNot(HaveOccurred()) Expect(image.CPUArchitecture).To(HaveValue(Equal(common.DefaultCPUArchitecture))) }) It("multiarch returns error", func() { - image, err := images.GetOsImage("4.11", common.MultiCPUArchitecture) + image, err := images.GetOsImage("4.11", common.MultiCPUArchitecture, "") Expect(err).Should(HaveOccurred()) Expect(image).Should(BeNil()) Expect(err.Error()).To(ContainSubstring("isn't specified in OS images list")) }) It("fetch OS image by major.minor", func() { - image, err := images.GetOsImage("4.9", common.DefaultCPUArchitecture) + image, err := images.GetOsImage("4.9", common.DefaultCPUArchitecture, "") Expect(err).ShouldNot(HaveOccurred()) Expect(image.OpenshiftVersion).To(HaveValue(Equal("4.9"))) }) It("With normalizing the CPU architecture", func() { - image, err := images.GetOsImage("4.9", common.AARCH64CPUArchitecture) + image, err := images.GetOsImage("4.9", common.AARCH64CPUArchitecture, "") Expect(err).ShouldNot(HaveOccurred()) Expect(image.Version).To(HaveValue(Equal("version-49.123-0_arm64"))) Expect(*image.CPUArchitecture).To(Equal(common.ARM64CPUArchitecture)) @@ -133,7 +133,7 @@ var _ = Describe("GetOsImage", func() { architectures := images.GetCPUArchitectures(version) for _, architecture := range architectures { - image, err := images.GetOsImage(version, architecture) + image, err := images.GetOsImage(version, architecture, "") Expect(err).ShouldNot(HaveOccurred()) for _, rhcos := range defaultOsImages { @@ -168,17 +168,114 @@ var _ = Describe("GetOsImage", func() { }) It("finds latest patch version by X.Y when given X.Y.Z", func() { - image, err := images.GetOsImage("4.10.1", common.DefaultCPUArchitecture) + image, err := images.GetOsImage("4.10.1", common.DefaultCPUArchitecture, "") Expect(err).ShouldNot(HaveOccurred()) Expect(image.OpenshiftVersion).To(HaveValue(Equal("4.10.10"))) }) It("finds latest patch version by X.Y when given X.Y", func() { - image, err := images.GetOsImage("4.10", common.DefaultCPUArchitecture) + image, err := images.GetOsImage("4.10", common.DefaultCPUArchitecture, "") Expect(err).ShouldNot(HaveOccurred()) Expect(image.OpenshiftVersion).To(HaveValue(Equal("4.10.10"))) }) }) + + Context("with OS streams", func() { + streamImages := func() models.OsImages { + return models.OsImages{ + { + OpenshiftVersion: swag.String("4.22"), + CPUArchitecture: swag.String(common.X86CPUArchitecture), + URL: swag.String("rhcos-rhel9"), + Version: swag.String("9.6.20260101-0"), + OsStream: swag.String("rhel-9"), + DefaultOsStream: swag.Bool(true), + }, + { + OpenshiftVersion: swag.String("4.22"), + CPUArchitecture: swag.String(common.X86CPUArchitecture), + URL: swag.String("rhcos-rhel10"), + Version: swag.String("10.0.20260101-0"), + OsStream: swag.String("rhel-10"), + DefaultOsStream: swag.Bool(false), + }, + } + } + + BeforeEach(func() { + var err error + images, err = NewOSImages(streamImages(), imageServiceEnabled) + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("returns the default stream when osStream is empty", func() { + image, err := images.GetOsImage("4.22", common.X86CPUArchitecture, "") + Expect(err).ShouldNot(HaveOccurred()) + Expect(swag.StringValue(image.OsStream)).To(Equal("rhel-9")) + Expect(swag.StringValue(image.Version)).To(Equal("9.6.20260101-0")) + }) + + It("returns the requested stream", func() { + image, err := images.GetOsImage("4.22", common.X86CPUArchitecture, "rhel-10") + Expect(err).ShouldNot(HaveOccurred()) + Expect(swag.StringValue(image.OsStream)).To(Equal("rhel-10")) + Expect(swag.StringValue(image.Version)).To(Equal("10.0.20260101-0")) + }) + + It("fails for an unknown stream", func() { + image, err := images.GetOsImage("4.22", common.X86CPUArchitecture, "rhel-11") + Expect(err).Should(HaveOccurred()) + Expect(image).To(BeNil()) + Expect(err.Error()).To(ContainSubstring("OS stream")) + }) + + It("fails when streams exist but none is marked default", func() { + noDefault := streamImages() + noDefault[0].DefaultOsStream = swag.Bool(false) + imgs, err := NewOSImages(noDefault, imageServiceEnabled) + Expect(err).ShouldNot(HaveOccurred()) + + image, err := imgs.GetOsImage("4.22", common.X86CPUArchitecture, "") + Expect(err).Should(HaveOccurred()) + Expect(image).To(BeNil()) + Expect(err.Error()).To(ContainSubstring("No default OS stream")) + }) + + It("fails when multiple defaults exist", func() { + multiDefault := streamImages() + multiDefault[1].DefaultOsStream = swag.Bool(true) + imgs, err := NewOSImages(multiDefault, imageServiceEnabled) + Expect(err).ShouldNot(HaveOccurred()) + + image, err := imgs.GetOsImage("4.22", common.X86CPUArchitecture, "") + Expect(err).Should(HaveOccurred()) + Expect(image).To(BeNil()) + Expect(err.Error()).To(ContainSubstring("Multiple default OS streams")) + }) + + It("keeps legacy first-match behavior when no stream metadata exists", func() { + legacy := models.OsImages{ + { + OpenshiftVersion: swag.String("4.22"), + CPUArchitecture: swag.String(common.X86CPUArchitecture), + URL: swag.String("rhcos-a"), + Version: swag.String("9.6.0"), + }, + { + OpenshiftVersion: swag.String("4.22"), + CPUArchitecture: swag.String(common.X86CPUArchitecture), + URL: swag.String("rhcos-b"), + Version: swag.String("10.0.0"), + }, + } + imgs, err := NewOSImages(legacy, imageServiceEnabled) + Expect(err).ShouldNot(HaveOccurred()) + + image, err := imgs.GetOsImage("4.22", common.X86CPUArchitecture, "") + Expect(err).ShouldNot(HaveOccurred()) + Expect(swag.StringValue(image.URL)).To(Equal("rhcos-a")) + }) + }) }) var _ = Describe("GetLatestOsImage", func() { @@ -186,7 +283,7 @@ var _ = Describe("GetLatestOsImage", func() { images, err := NewOSImages(defaultOsImages[0:1], imageServiceEnabled) Expect(err).ShouldNot(HaveOccurred()) - osImage, err := images.GetLatestOsImage(common.TestDefaultConfig.CPUArchitecture) + osImage, err := images.GetLatestOsImage(common.TestDefaultConfig.CPUArchitecture, "") Expect(err).ShouldNot(HaveOccurred()) Expect(*osImage.OpenshiftVersion).Should(Equal("4.11.1")) Expect(*osImage.CPUArchitecture).Should(Equal(common.TestDefaultConfig.CPUArchitecture)) @@ -196,7 +293,7 @@ var _ = Describe("GetLatestOsImage", func() { images, err := NewOSImages(defaultOsImages, imageServiceEnabled) Expect(err).ShouldNot(HaveOccurred()) - osImage, err := images.GetLatestOsImage(common.TestDefaultConfig.CPUArchitecture) + osImage, err := images.GetLatestOsImage(common.TestDefaultConfig.CPUArchitecture, "") Expect(err).ShouldNot(HaveOccurred()) Expect(*osImage.OpenshiftVersion).Should(Equal("4.11.1")) Expect(*osImage.CPUArchitecture).Should(Equal(common.TestDefaultConfig.CPUArchitecture)) @@ -206,7 +303,7 @@ var _ = Describe("GetLatestOsImage", func() { images, err := NewOSImages(defaultOsImages, imageServiceEnabled) Expect(err).ShouldNot(HaveOccurred()) - osImage, err := images.GetLatestOsImage(common.MultiCPUArchitecture) + osImage, err := images.GetLatestOsImage(common.MultiCPUArchitecture, "") Expect(err).Should(HaveOccurred()) Expect(osImage).Should(BeNil()) Expect(err.Error()).To(ContainSubstring("No OS images are available")) @@ -225,33 +322,33 @@ var _ = Describe("GetOsImageOrLatest", func() { }) It("successfully gets an OS image with a valid openshift version and cpu architecture", func() { - image, err := images.GetOsImageOrLatest("4.9", common.TestDefaultConfig.CPUArchitecture) + image, err := images.GetOsImageOrLatest("4.9", common.TestDefaultConfig.CPUArchitecture, "") Expect(err).To(BeNil()) Expect(*image.OpenshiftVersion).Should(Equal("4.9")) Expect(*image.CPUArchitecture).Should(Equal(common.TestDefaultConfig.CPUArchitecture)) }) It("successfully gets the latest OS image with a valid cpu architecture", func() { - image, err := images.GetOsImageOrLatest("", common.TestDefaultConfig.CPUArchitecture) + image, err := images.GetOsImageOrLatest("", common.TestDefaultConfig.CPUArchitecture, "") Expect(err).To(BeNil()) Expect(*image.OpenshiftVersion).Should(Equal("4.11.1")) Expect(*image.CPUArchitecture).Should(Equal(common.TestDefaultConfig.CPUArchitecture)) }) It("fails to get OS images for invalid cpu architecture and valid openshift version", func() { - image, err := images.GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "x866") + image, err := images.GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "x866", "") Expect(err).ToNot(BeNil()) Expect(image).Should(BeNil()) }) It("fails to get OS images for invalid cpu architecture and invalid openshift version", func() { - image, err := images.GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "x866") + image, err := images.GetOsImageOrLatest(common.TestDefaultConfig.OpenShiftVersion, "x866", "") Expect(err).ToNot(BeNil()) Expect(image).Should(BeNil()) }) It("fails to get OS images for invalid cpu architecture and no openshift version", func() { - image, err := images.GetOsImageOrLatest("", "x866") + image, err := images.GetOsImageOrLatest("", "x866", "") Expect(err).ToNot(BeNil()) Expect(image).Should(BeNil()) }) diff --git a/models/cluster.go b/models/cluster.go index 1f54de6a02d7..e529fc724046 100644 --- a/models/cluster.go +++ b/models/cluster.go @@ -230,6 +230,9 @@ type Cluster struct { // Indication if organization soft timeouts is enabled for the cluster. OrgSoftTimeoutsEnabled bool `json:"org_soft_timeouts_enabled,omitempty"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/models/cluster_create_params.go b/models/cluster_create_params.go index 0f72526515bd..74802da9c36a 100644 --- a/models/cluster_create_params.go +++ b/models/cluster_create_params.go @@ -127,6 +127,9 @@ type ClusterCreateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/models/infra_env.go b/models/infra_env.go index c634fed9d391..418a7199cf49 100644 --- a/models/infra_env.go +++ b/models/infra_env.go @@ -94,6 +94,9 @@ type InfraEnv struct { // org id OrgID string `json:"org_id,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/models/infra_env_create_params.go b/models/infra_env_create_params.go index c8c3e2befc76..210fd9310cec 100644 --- a/models/infra_env_create_params.go +++ b/models/infra_env_create_params.go @@ -63,6 +63,9 @@ type InfraEnvCreateParams struct { // Version of the OpenShift cluster (used to infer the RHCOS version - temporary until generic logic implemented). OpenshiftVersion string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/models/infra_env_update_params.go b/models/infra_env_update_params.go index 08c46bdf87a1..fa33b70cbf46 100644 --- a/models/infra_env_update_params.go +++ b/models/infra_env_update_params.go @@ -47,6 +47,9 @@ type InfraEnvUpdateParams struct { // Version of the OS image OpenshiftVersion *string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream *string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/models/os_image.go b/models/os_image.go index 5d7cf67d8fe1..58cf88d3aefa 100644 --- a/models/os_image.go +++ b/models/os_image.go @@ -25,11 +25,17 @@ type OsImage struct { // Enum: [x86_64 aarch64 arm64 ppc64le s390x] CPUArchitecture *string `json:"cpu_architecture" gorm:"default:'x86_64'"` + // Whether this OS image is the default stream for its OpenShift version and CPU architecture. + DefaultOsStream *bool `json:"default_os_stream,omitempty"` + // Version of the operating system image // Example: 4.12 // Required: true OpenshiftVersion *string `json:"openshift_version"` + // The OS stream of this image (e.g. rhel-9, rhel-10). + OsStream *string `json:"os_stream,omitempty"` + // The base OS image used for the discovery iso. // Required: true URL *string `json:"url"` diff --git a/models/v2_cluster_update_params.go b/models/v2_cluster_update_params.go index 979f98035982..0c9f0b310af1 100644 --- a/models/v2_cluster_update_params.go +++ b/models/v2_cluster_update_params.go @@ -116,6 +116,9 @@ type V2ClusterUpdateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream *string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook.go b/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook.go index 2cc08afb3973..0e35452cc1a7 100644 --- a/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook.go +++ b/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook.go @@ -198,9 +198,9 @@ func (a *InfraEnvValidatingAdmissionHook) validateCreate(admissionSpec *admissio } } - // Ensure that ClusterRef and OSImageVersion are not both specified - if newObject.Spec.ClusterRef != nil && newObject.Spec.OSImageVersion != "" { - message := "Either Spec.ClusterRef or Spec.OSImageVersion should be specified (not both)." + // Ensure that ClusterRef cannot be combined with OSImageVersion or OSStream + if newObject.Spec.ClusterRef != nil && (newObject.Spec.OSImageVersion != "" || newObject.Spec.OSStream != "") { + message := "Spec.ClusterRef is mutually exclusive with either Spec.OSImageVersion or Spec.OSStream." contextLogger.Infof("Failed validation: %v", message) contextLogger.Error(message) return &admissionv1.AdmissionResponse{ @@ -294,6 +294,19 @@ func (a *InfraEnvValidatingAdmissionHook) validateUpdate(admissionSpec *admissio } } + if !a.osStreamValid(contextLogger, oldObject, newObject) { + message := "spec.OSStream is not valid. It can't be added alongside with a clusterRef when the cluster is not installed yet." + contextLogger.Infof("Failed validation: %v", message) + + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, + Message: message, + }, + } + } + // If we get here, then all checks passed, so the object is valid. contextLogger.Info("Successful validation") return &admissionv1.AdmissionResponse{ @@ -303,6 +316,17 @@ func (a *InfraEnvValidatingAdmissionHook) validateUpdate(admissionSpec *admissio // osImageVersionValid checks if the OSImageVersion is valid: if it has been added, then the cluster must be installed. func (a *InfraEnvValidatingAdmissionHook) osImageVersionValid(logger *log.Entry, oldObject *v1beta1.InfraEnv, newObject *v1beta1.InfraEnv) bool { + return a.isFieldAddedOnlyWhenClusterIsInstalled(logger, oldObject, newObject, + oldObject.Spec.OSImageVersion == "" && newObject.Spec.OSImageVersion != "") +} + +// osStreamValid checks if the OSStream is valid: if it has been added, then the cluster must be installed. +func (a *InfraEnvValidatingAdmissionHook) osStreamValid(logger *log.Entry, oldObject *v1beta1.InfraEnv, newObject *v1beta1.InfraEnv) bool { + return a.isFieldAddedOnlyWhenClusterIsInstalled(logger, oldObject, newObject, + oldObject.Spec.OSStream == "" && newObject.Spec.OSStream != "") +} + +func (a *InfraEnvValidatingAdmissionHook) isFieldAddedOnlyWhenClusterIsInstalled(logger *log.Entry, oldObject *v1beta1.InfraEnv, newObject *v1beta1.InfraEnv, fieldAdded bool) bool { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -311,8 +335,8 @@ func (a *InfraEnvValidatingAdmissionHook) osImageVersionValid(logger *log.Entry, return true } - // If OSImageVersion was not added, then there is nothing to check - if !(oldObject.Spec.OSImageVersion == "" && newObject.Spec.OSImageVersion != "") { + // If the field was not added, then there is nothing to check + if !fieldAdded { return true } diff --git a/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook_test.go b/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook_test.go index 3aa2765cdad0..bc1f8c39a2b0 100644 --- a/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook_test.go +++ b/pkg/webhooks/agentinstall/v1beta1/infraenv_admission_hook_test.go @@ -390,6 +390,94 @@ var _ = Describe("infraenv web validate", func() { return createTestClient() }, }, + { + name: "Test can't specify both Spec.ClusterRef and Spec.OSStream", + newSpec: v1beta1.InfraEnvSpec{ + ClusterRef: &v1beta1.ClusterReference{ + Name: "newName", + Namespace: "newName", + }, + OSStream: "rhel-10", + }, + operation: admissionv1.Create, + expectedAllowed: false, + }, + { + name: "Test InfraEnv create does not fail when only Spec.OSStream is specified", + newSpec: v1beta1.InfraEnvSpec{ + OSStream: "rhel-10", + }, + operation: admissionv1.Create, + expectedAllowed: true, + }, + { + name: "Test OSStream can be added when cluster is installed", + newSpec: v1beta1.InfraEnvSpec{ + ClusterRef: &v1beta1.ClusterReference{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + OSStream: "rhel-10", + }, + oldSpec: v1beta1.InfraEnvSpec{ + ClusterRef: &v1beta1.ClusterReference{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + }, + operation: admissionv1.Update, + expectedAllowed: true, + setupClient: func() client.Client { + cd := &hivev1.ClusterDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + Spec: hivev1.ClusterDeploymentSpec{ + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Kind: "AgentClusterInstall", + Name: "test-aci", + }, + Installed: true, + }, + } + return createTestClient(cd) + }, + }, + { + name: "Test OSStream cannot be added when cluster is not installed", + newSpec: v1beta1.InfraEnvSpec{ + ClusterRef: &v1beta1.ClusterReference{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + OSStream: "rhel-10", + }, + oldSpec: v1beta1.InfraEnvSpec{ + ClusterRef: &v1beta1.ClusterReference{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + }, + operation: admissionv1.Update, + expectedAllowed: false, + setupClient: func() client.Client { + cd := &hivev1.ClusterDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + Spec: hivev1.ClusterDeploymentSpec{ + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Kind: "AgentClusterInstall", + Name: "test-aci", + }, + Installed: false, + }, + } + return createTestClient(cd) + }, + }, } for i := range cases { diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 1861b977d777..b04b84c271a2 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -6890,6 +6890,10 @@ func init() { "description": "Indication if organization soft timeouts is enabled for the cluster.", "type": "boolean" }, + "os_stream": { + "description": "The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used.", + "type": "string" + }, "platform": { "$ref": "#/definitions/platform" }, @@ -7185,6 +7189,10 @@ func init() { "$ref": "#/definitions/bundle-create-params" } }, + "os_stream": { + "description": "The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used.", + "type": "string" + }, "platform": { "x-nullable": true, "$ref": "#/definitions/platform" @@ -9343,6 +9351,10 @@ func init() { "org_id": { "type": "string" }, + "os_stream": { + "description": "The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream.", + "type": "string" + }, "proxy": { "$ref": "#/definitions/proxy" }, @@ -9456,6 +9468,10 @@ func init() { "description": "Version of the OpenShift cluster (used to infer the RHCOS version - temporary until generic logic implemented).", "type": "string" }, + "os_stream": { + "description": "The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream.", + "type": "string" + }, "proxy": { "$ref": "#/definitions/proxy" }, @@ -9528,6 +9544,11 @@ func init() { "type": "string", "x-nullable": true }, + "os_stream": { + "description": "The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream.", + "type": "string", + "x-nullable": true + }, "proxy": { "$ref": "#/definitions/proxy" }, @@ -10605,11 +10626,21 @@ func init() { ], "x-go-custom-tag": "gorm:\"default:'x86_64'\"" }, + "default_os_stream": { + "description": "Whether this OS image is the default stream for its OpenShift version and CPU architecture.", + "type": "boolean", + "x-nullable": true + }, "openshift_version": { "description": "Version of the operating system image", "type": "string", "example": "4.12" }, + "os_stream": { + "description": "The OS stream of this image (e.g. rhel-9, rhel-10).", + "type": "string", + "x-nullable": true + }, "url": { "description": "The base OS image used for the discovery iso.", "type": "string" @@ -11394,6 +11425,11 @@ func init() { "$ref": "#/definitions/bundle-create-params" } }, + "os_stream": { + "description": "The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used.", + "type": "string", + "x-nullable": true + }, "platform": { "$ref": "#/definitions/platform" }, @@ -18683,6 +18719,10 @@ func init() { "description": "Indication if organization soft timeouts is enabled for the cluster.", "type": "boolean" }, + "os_stream": { + "description": "The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used.", + "type": "string" + }, "platform": { "$ref": "#/definitions/platform" }, @@ -18978,6 +19018,10 @@ func init() { "$ref": "#/definitions/bundle-create-params" } }, + "os_stream": { + "description": "The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used.", + "type": "string" + }, "platform": { "x-nullable": true, "$ref": "#/definitions/platform" @@ -21105,6 +21149,10 @@ func init() { "org_id": { "type": "string" }, + "os_stream": { + "description": "The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream.", + "type": "string" + }, "proxy": { "$ref": "#/definitions/proxy" }, @@ -21220,6 +21268,10 @@ func init() { "description": "Version of the OpenShift cluster (used to infer the RHCOS version - temporary until generic logic implemented).", "type": "string" }, + "os_stream": { + "description": "The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream.", + "type": "string" + }, "proxy": { "$ref": "#/definitions/proxy" }, @@ -21293,6 +21345,11 @@ func init() { "type": "string", "x-nullable": true }, + "os_stream": { + "description": "The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream.", + "type": "string", + "x-nullable": true + }, "proxy": { "$ref": "#/definitions/proxy" }, @@ -22355,11 +22412,21 @@ func init() { ], "x-go-custom-tag": "gorm:\"default:'x86_64'\"" }, + "default_os_stream": { + "description": "Whether this OS image is the default stream for its OpenShift version and CPU architecture.", + "type": "boolean", + "x-nullable": true + }, "openshift_version": { "description": "Version of the operating system image", "type": "string", "example": "4.12" }, + "os_stream": { + "description": "The OS stream of this image (e.g. rhel-9, rhel-10).", + "type": "string", + "x-nullable": true + }, "url": { "description": "The base OS image used for the discovery iso.", "type": "string" @@ -23118,6 +23185,11 @@ func init() { "$ref": "#/definitions/bundle-create-params" } }, + "os_stream": { + "description": "The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used.", + "type": "string", + "x-nullable": true + }, "platform": { "$ref": "#/definitions/platform" }, diff --git a/swagger.yaml b/swagger.yaml index 852a32e1bc8a..fb45b1861fcb 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -5202,6 +5202,9 @@ definitions: ocp_release_image: type: string description: OpenShift release image URI. + os_stream: + type: string + description: The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. base_dns_domain: type: string description: Base domain of the cluster. All DNS records must be sub-domains of this base and include the cluster name. @@ -5578,6 +5581,10 @@ definitions: x-nullable: true load_balancer: $ref: '#/definitions/load_balancer' + os_stream: + type: string + description: The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + x-nullable: true import-cluster-params: type: object @@ -5664,6 +5671,9 @@ definitions: type: string format: uuid description: Cluster ID on OCP system. + os_stream: + type: string + description: The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. image_info: $ref: '#/definitions/image_info' platform: @@ -7660,6 +7670,14 @@ definitions: version: type: string description: Build ID of the OS image. + os_stream: + type: string + description: The OS stream of this image (e.g. rhel-9, rhel-10). + x-nullable: true + default_os_stream: + type: boolean + description: Whether this OS image is the default stream for its OpenShift version and CPU architecture. + x-nullable: true os-images: type: array @@ -7869,6 +7887,9 @@ definitions: type: string email_domain: type: string + os_stream: + type: string + description: The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. proxy: $ref: "#/definitions/proxy" additional_ntp_sources: @@ -8050,6 +8071,9 @@ definitions: # TODO: remove arm64 when AI moves to using aarch64 enum: ['x86_64', 'aarch64', 'arm64','ppc64le','s390x'] description: The CPU architecture of the image (x86_64/arm64/etc). + os_stream: + type: string + description: The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. kernel_arguments: $ref: '#/definitions/kernel_arguments' additional_trust_bundle: @@ -8117,6 +8141,10 @@ definitions: type: string description: Version of the OS image x-nullable: true + os_stream: + type: string + description: The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + x-nullable: true network_discovery_delay_seconds: type: integer format: int64 diff --git a/vendor/github.com/openshift/assisted-service/api/hiveextension/v1beta1/agentclusterinstall_types.go b/vendor/github.com/openshift/assisted-service/api/hiveextension/v1beta1/agentclusterinstall_types.go index 63560a108b74..5e66f3e286ca 100644 --- a/vendor/github.com/openshift/assisted-service/api/hiveextension/v1beta1/agentclusterinstall_types.go +++ b/vendor/github.com/openshift/assisted-service/api/hiveextension/v1beta1/agentclusterinstall_types.go @@ -231,6 +231,11 @@ type AgentClusterInstallSpec struct { // LoadBalancer defines the load balancer used by the cluster for ingress traffic. // +optional LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` + + // OSStream is the OS stream to use for this cluster (e.g. rhel-9, rhel-10). + // If unset, the default OS stream for the OpenShift version is used. + // +optional + OSStream string `json:"osStream,omitempty"` } // IgnitionEndpoint stores the data to of the custom ignition endpoint. diff --git a/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go b/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go index 9458f210b638..6b54e9921122 100644 --- a/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go +++ b/vendor/github.com/openshift/assisted-service/api/v1beta1/agentserviceconfig_types.go @@ -39,6 +39,13 @@ type OSImage struct { // The CPU architecture of the image (x86_64/arm64/etc). // +optional CPUArchitecture string `json:"cpuArchitecture"` + // OSStream is the OS stream of this image (e.g. rhel-9, rhel-10). + // +optional + OSStream string `json:"osStream,omitempty"` + // DefaultOSStream indicates whether this OS image is the default stream + // for its OpenShift version and CPU architecture. + // +optional + DefaultOSStream *bool `json:"defaultOsStream,omitempty"` } type MustGatherImage struct { diff --git a/vendor/github.com/openshift/assisted-service/api/v1beta1/infraenv_types.go b/vendor/github.com/openshift/assisted-service/api/v1beta1/infraenv_types.go index 7b9fa4329814..0721b6a67728 100644 --- a/vendor/github.com/openshift/assisted-service/api/v1beta1/infraenv_types.go +++ b/vendor/github.com/openshift/assisted-service/api/v1beta1/infraenv_types.go @@ -133,6 +133,12 @@ type InfraEnvSpec struct { // +optional OSImageVersion string `json:"osImageVersion,omitempty"` + // OSStream is the OS stream to use when generating the InfraEnv (e.g. rhel-9, rhel-10). + // If unset and ClusterRef is set, the cluster's OS stream is used. + // Note: OSStream can't be specified along with ClusterRef while creating an InfraEnv. + // +optional + OSStream string `json:"osStream,omitempty"` + // MirrorRegistryRef references a ConfigMap containing mirror registry configuration in TOML format. // The referenced ConfigMap should contain 'registries.conf' and optionally 'ca-bundle.crt' keys. // This configuration is embedded into the discovery image so that agents can pull container images diff --git a/vendor/github.com/openshift/assisted-service/models/cluster.go b/vendor/github.com/openshift/assisted-service/models/cluster.go index 1f54de6a02d7..e529fc724046 100644 --- a/vendor/github.com/openshift/assisted-service/models/cluster.go +++ b/vendor/github.com/openshift/assisted-service/models/cluster.go @@ -230,6 +230,9 @@ type Cluster struct { // Indication if organization soft timeouts is enabled for the cluster. OrgSoftTimeoutsEnabled bool `json:"org_soft_timeouts_enabled,omitempty"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go b/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go index 0f72526515bd..74802da9c36a 100644 --- a/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go +++ b/vendor/github.com/openshift/assisted-service/models/cluster_create_params.go @@ -127,6 +127,9 @@ type ClusterCreateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"` diff --git a/vendor/github.com/openshift/assisted-service/models/infra_env.go b/vendor/github.com/openshift/assisted-service/models/infra_env.go index c634fed9d391..418a7199cf49 100644 --- a/vendor/github.com/openshift/assisted-service/models/infra_env.go +++ b/vendor/github.com/openshift/assisted-service/models/infra_env.go @@ -94,6 +94,9 @@ type InfraEnv struct { // org id OrgID string `json:"org_id,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go b/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go index c8c3e2befc76..210fd9310cec 100644 --- a/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go +++ b/vendor/github.com/openshift/assisted-service/models/infra_env_create_params.go @@ -63,6 +63,9 @@ type InfraEnvCreateParams struct { // Version of the OpenShift cluster (used to infer the RHCOS version - temporary until generic logic implemented). OpenshiftVersion string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go b/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go index 08c46bdf87a1..fa33b70cbf46 100644 --- a/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go +++ b/vendor/github.com/openshift/assisted-service/models/infra_env_update_params.go @@ -47,6 +47,9 @@ type InfraEnvUpdateParams struct { // Version of the OS image OpenshiftVersion *string `json:"openshift_version,omitempty"` + // The OS stream to use for this infra-env (e.g. rhel-9, rhel-10). If unset, inherits from the associated cluster or uses the default OS stream. + OsStream *string `json:"os_stream,omitempty"` + // proxy Proxy *Proxy `json:"proxy,omitempty" gorm:"embedded;embeddedPrefix:proxy_"` diff --git a/vendor/github.com/openshift/assisted-service/models/os_image.go b/vendor/github.com/openshift/assisted-service/models/os_image.go index 5d7cf67d8fe1..58cf88d3aefa 100644 --- a/vendor/github.com/openshift/assisted-service/models/os_image.go +++ b/vendor/github.com/openshift/assisted-service/models/os_image.go @@ -25,11 +25,17 @@ type OsImage struct { // Enum: [x86_64 aarch64 arm64 ppc64le s390x] CPUArchitecture *string `json:"cpu_architecture" gorm:"default:'x86_64'"` + // Whether this OS image is the default stream for its OpenShift version and CPU architecture. + DefaultOsStream *bool `json:"default_os_stream,omitempty"` + // Version of the operating system image // Example: 4.12 // Required: true OpenshiftVersion *string `json:"openshift_version"` + // The OS stream of this image (e.g. rhel-9, rhel-10). + OsStream *string `json:"os_stream,omitempty"` + // The base OS image used for the discovery iso. // Required: true URL *string `json:"url"` diff --git a/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go b/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go index 979f98035982..0c9f0b310af1 100644 --- a/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go +++ b/vendor/github.com/openshift/assisted-service/models/v2_cluster_update_params.go @@ -116,6 +116,9 @@ type V2ClusterUpdateParams struct { // OperatorBundles []*BundleCreateParams `json:"operator_bundles"` + // The OS stream to use for this cluster (e.g. rhel-9, rhel-10). If unset, the default OS stream for the OpenShift version is used. + OsStream *string `json:"os_stream,omitempty"` + // platform Platform *Platform `json:"platform,omitempty" gorm:"embedded;embeddedPrefix:platform_"`