Repository: openshift/assisted-service (language: python)

Issue:
OCPBUGS-33227: Use openshift-install binary for releases >= 4.16
In 4.16 the installer binaries started to be built against EL9 libraries rather than EL8. Additionally support for baremetal installs was added to the `openshift-install` binary which was also made statically linked.

This means that using `openshift-baremetal-install` from a 4.16 release on an EL8-based image will cause errors like:

```
Failed to prepare the installation due to an unexpected error: failed generating install config for cluster 47ff23ae-012a-421e-89a9-8ae1ca04a67f: error running openshift-install manifests, /data/install-config-generate/installercache/quay.io/openshift-release-dev/ocp-release:4.16.0-rc.0-x86_64/ln_1715091595_openshift-baremetal-install: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by /data/install-config-generate/installercache/quay.io/openshift-release-dev/ocp-release:4.16.0-rc.0-x86_64/ln_1715091595_openshift-baremetal-install)
```

To address this, this commit moves installs for clusters >= 4.16 to use `openshift-install` which will work correctly regardless of the container base image it is run on. This is only possible for 4.16 and greater releases because the changes that allow `openshift-install` to do baremetal installations (and to statically link it) were only made for 4.16 and will not be backported to older releases.

This is a temporary fix as installing FIPS compliant clusters will still require using `openshift-baremetal-install` so the ultimate goal is to be able to run `openshift-baremetal-install` for any supported OCP version.

Note that this is only relevant for the case where assisted-service is an el8-based image, which is not the case currently. This PR is mostly meant for backport to earlier versions which will still need to install 4.16.

## List all the issues related to this PR

Related to https://issues.redhat.com/browse/CORS-3024
Resolves https://issues.redhat.com/browse/OCPBUGS-33227

- [ ] New Feature <!-- new functionality -->
- [ ] Enhancement <!-- refactor, code changes, improvement, that won't add new features -->
- [x] Bug fix
- [ ] Tests
- [ ] Documentation
- [ ] CI/CD <!-- Notice that changes for Dockerfiles/Jenkinsfiles aren't tested in CI due to a known bug. -->

## What environments does this code impact?

- [ ] Automation (CI, tools, etc)
- [x] Cloud
- [x] Operator Managed Deployments
- [ ] None

## How was this code tested?

Deployed manually and installed both 4.15 and 4.16 SNO successfully.
Also provided the patched image to telco QE for testing.

<!-- Please, select one or more if needed: -->

- [ ] assisted-test-infra environment
- [x] dev-scripts environment
- [ ] Reviewer's test appreciated
- [x] Waiting for CI to do a full test run
- [x] Manual (Elaborate on how it was tested)
- [ ] No tests needed

## Checklist

- [x] Title and description added to both, commit and PR.
- [x] Relevant issues have been associated (see [CONTRIBUTING] guide)
- [x] This change does not require a documentation update (docstring, `docs`, README, etc)
- [x] Does this change include unit-tests (note that code changes require unit-tests)

## Reviewers Checklist

- Are the title and description (in both PR and commit) meaningful and clear?
- Is there a bug required (and linked) for this change?
- Should this PR be backported?



Discussion / hints:

Fix patch:
diff --git a/internal/ignition/ignition.go b/internal/ignition/ignition.go
index 892bbfa4635..78bdf62269c 100644
--- a/internal/ignition/ignition.go
+++ b/internal/ignition/ignition.go
@@ -331,7 +331,7 @@ func (g *installerGenerator) Generate(ctx context.Context, installConfig []byte,
 		MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay}, mirrorRegistriesBuilder)
 
 	release, err := g.installerCache.Get(g.installerReleaseImageOverride, g.releaseImageMirror,
-		g.cluster.PullSecret, ocRelease)
+		g.cluster.PullSecret, ocRelease, g.cluster.OpenshiftVersion)
 	if err != nil {
 		return errors.Wrap(err, "failed to get installer path")
 	}
diff --git a/internal/installercache/installercache.go b/internal/installercache/installercache.go
index a67090d760b..cf1d21f34fd 100644
--- a/internal/installercache/installercache.go
+++ b/internal/installercache/installercache.go
@@ -63,7 +63,7 @@ func New(cacheDir string, storageCapacity int64, log logrus.FieldLogger) *Instal
 // Get returns the path to an openshift-baremetal-install binary extracted from
 // the referenced release image. Tries the mirror release image first if it's set. It is safe for concurrent use. A cache of
 // binaries is maintained to reduce re-downloading of the same release.
-func (i *Installers) Get(releaseID, releaseIDMirror, pullSecret string, ocRelease oc.Release) (*Release, error) {
+func (i *Installers) Get(releaseID, releaseIDMirror, pullSecret string, ocRelease oc.Release, ocpVersion string) (*Release, error) {
 	i.Lock()
 	defer i.Unlock()
 
@@ -74,13 +74,16 @@ func (i *Installers) Get(releaseID, releaseIDMirror, pullSecret string, ocReleas
 	if releaseIDMirror != "" {
 		releaseImageLocation = releaseIDMirror
 	}
-	workdir, binary, path = ocRelease.GetReleaseBinaryPath(releaseImageLocation, i.cacheDir)
+	workdir, binary, path, err = ocRelease.GetReleaseBinaryPath(releaseImageLocation, i.cacheDir, ocpVersion)
+	if err != nil {
+		return nil, err
+	}
 	if _, err = os.Stat(path); os.IsNotExist(err) {
 		//evict older files if necessary
 		i.evict()
 
 		//extract the binary
-		_, err = ocRelease.Extract(i.log, releaseID, releaseIDMirror, i.cacheDir, pullSecret)
+		_, err = ocRelease.Extract(i.log, releaseID, releaseIDMirror, i.cacheDir, pullSecret, ocpVersion)
 		if err != nil {
 			return &Release{}, err
 		}
diff --git a/internal/oc/mock_release.go b/internal/oc/mock_release.go
index 661ae3e5594..c1184ffbbfe 100644
--- a/internal/oc/mock_release.go
+++ b/internal/oc/mock_release.go
@@ -35,18 +35,18 @@ func (m *MockRelease) EXPECT() *MockReleaseMockRecorder {
 }
 
 // Extract mocks base method.
-func (m *MockRelease) Extract(log logrus.FieldLogger, releaseImage, releaseImageMirror, cacheDir, pullSecret string) (string, error) {
+func (m *MockRelease) Extract(log logrus.FieldLogger, releaseImage, releaseImageMirror, cacheDir, pullSecret, ocpVersion string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Extract", log, releaseImage, releaseImageMirror, cacheDir, pullSecret)
+	ret := m.ctrl.Call(m, "Extract", log, releaseImage, releaseImageMirror, cacheDir, pullSecret, ocpVersion)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // Extract indicates an expected call of Extract.
-func (mr *MockReleaseMockRecorder) Extract(log, releaseImage, releaseImageMirror, cacheDir, pullSecret interface{}) *gomock.Call {
+func (mr *MockReleaseMockRecorder) Extract(log, releaseImage, releaseImageMirror, cacheDir, pullSecret, ocpVersion interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Extract", reflect.TypeOf((*MockRelease)(nil).Extract), log, releaseImage, releaseImageMirror, cacheDir, pullSecret)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Extract", reflect.TypeOf((*MockRelease)(nil).Extract), log, releaseImage, releaseImageMirror, cacheDir, pullSecret, ocpVersion)
 }
 
 // GetIronicAgentImage mocks base method.
@@ -155,17 +155,18 @@ func (mr *MockReleaseMockRecorder) GetReleaseArchitecture(log, releaseImage, rel
 }
 
 // GetReleaseBinaryPath mocks base method.
-func (m *MockRelease) GetReleaseBinaryPath(releaseImage, cacheDir string) (string, string, string) {
+func (m *MockRelease) GetReleaseBinaryPath(releaseImage, cacheDir, ocpVersion string) (string, string, string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetReleaseBinaryPath", releaseImage, cacheDir)
+	ret := m.ctrl.Call(m, "GetReleaseBinaryPath", releaseImage, cacheDir, ocpVersion)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(string)
 	ret2, _ := ret[2].(string)
-	return ret0, ret1, ret2
+	ret3, _ := ret[3].(error)
+	return ret0, ret1, ret2, ret3
 }
 
 // GetReleaseBinaryPath indicates an expected call of GetReleaseBinaryPath.
-func (mr *MockReleaseMockRecorder) GetReleaseBinaryPath(releaseImage, cacheDir interface{}) *gomock.Call {
+func (mr *MockReleaseMockRecorder) GetReleaseBinaryPath(releaseImage, cacheDir, ocpVersion interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReleaseBinaryPath", reflect.TypeOf((*MockRelease)(nil).GetReleaseBinaryPath), releaseImage, cacheDir)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReleaseBinaryPath", reflect.TypeOf((*MockRelease)(nil).GetReleaseBinaryPath), releaseImage, cacheDir, ocpVersion)
 }
diff --git a/internal/oc/release.go b/internal/oc/release.go
index 21a925ba9bf..21f827c4c49 100644
--- a/internal/oc/release.go
+++ b/internal/oc/release.go
@@ -24,12 +24,13 @@ import (
 )
 
 const (
-	mcoImageName         = "machine-config-operator"
-	ironicAgentImageName = "ironic-agent"
-	mustGatherImageName  = "must-gather"
-	okdRPMSImageName     = "okd-rpms"
-	DefaultTries         = 5
-	DefaltRetryDelay     = time.Second * 5
+	mcoImageName                   = "machine-config-operator"
+	ironicAgentImageName           = "ironic-agent"
+	mustGatherImageName            = "must-gather"
+	okdRPMSImageName               = "okd-rpms"
+	DefaultTries                   = 5
+	DefaltRetryDelay               = time.Second * 5
+	staticInstallerRequiredVersion = "4.16.0-ec.6"
 )
 
 type Config struct {
@@ -46,8 +47,8 @@ type Release interface {
 	GetOpenshiftVersion(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, pullSecret string) (string, error)
 	GetMajorMinorVersion(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, pullSecret string) (string, error)
 	GetReleaseArchitecture(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, pullSecret string) ([]string, error)
-	GetReleaseBinaryPath(releaseImage string, cacheDir string) (workdir string, binary string, path string)
-	Extract(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string) (string, error)
+	GetReleaseBinaryPath(releaseImage string, cacheDir string, ocpVersion string) (workdir string, binary string, path string, err error)
+	Extract(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string, ocpVersion string) (string, error)
 }
 
 type imageValue struct {
@@ -311,9 +312,9 @@ func (r *release) getOpenshiftVersionFromRelease(log logrus.FieldLogger, release
 	return strings.Trim(version, "'"), nil
 }
 
-// Extract openshift-baremetal-install binary from releaseImageMirror if provided.
+// Extract installer binary from releaseImageMirror if provided.
 // Else extract from the source releaseImage
-func (r *release) Extract(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string) (string, error) {
+func (r *release) Extract(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string, ocpVersion string) (string, error) {
 	var path string
 	var err error
 	if releaseImage == "" && releaseImageMirror == "" {
@@ -328,13 +329,13 @@ func (r *release) Extract(log logrus.FieldLogger, releaseImage string, releaseIm
 
 	if releaseImageMirror != "" {
 		//TODO: Get mirror registry certificate from install-config
-		path, err = r.extractFromRelease(log, releaseImageMirror, cacheDir, pullSecret, true, icspFile)
+		path, err = r.extractFromRelease(log, releaseImageMirror, cacheDir, pullSecret, true, icspFile, ocpVersion)
 		if err != nil {
 			log.WithError(err).Errorf("failed to extract openshift-baremetal-install from mirror release image %s", releaseImageMirror)
 			return "", err
 		}
 	} else {
-		path, err = r.extractFromRelease(log, releaseImage, cacheDir, pullSecret, false, icspFile)
+		path, err = r.extractFromRelease(log, releaseImage, cacheDir, pullSecret, false, icspFile, ocpVersion)
 		if err != nil {
 			log.WithError(err).Errorf("failed to extract openshift-baremetal-install from release image %s", releaseImage)
 			return "", err
@@ -343,19 +344,35 @@ func (r *release) Extract(log logrus.FieldLogger, releaseImage string, releaseIm
 	return path, err
 }
 
-func (r *release) GetReleaseBinaryPath(releaseImage string, cacheDir string) (workdir string, binary string, path string) {
-	workdir = filepath.Join(cacheDir, releaseImage)
+func (r *release) GetReleaseBinaryPath(releaseImage string, cacheDir string, ocpVersion string) (workdir string, binary string, path string, err error) {
 	binary = "openshift-baremetal-install"
+
+	// use the statically linked binary for 4.16 and up since our container is el8
+	// based and the baremetal binary for those versions is dynamically linked
+	// against el9 libaries
+	staticLinkingRequiredVersion := version.Must(version.NewVersion(staticInstallerRequiredVersion))
+	v, err := version.NewVersion(ocpVersion)
+	if err != nil {
+		return "", "", "", err
+	}
+	if v.GreaterThanOrEqual(staticLinkingRequiredVersion) {
+		binary = "openshift-install"
+	}
+
+	workdir = filepath.Join(cacheDir, releaseImage)
 	path = filepath.Join(workdir, binary)
 	return
 }
 
 // extractFromRelease returns the path to an openshift-baremetal-install binary extracted from
 // the referenced release image.
-func (r *release) extractFromRelease(log logrus.FieldLogger, releaseImage, cacheDir, pullSecret string, insecure bool, icspFile string) (string, error) {
-	workdir, binary, path := r.GetReleaseBinaryPath(releaseImage, cacheDir)
+func (r *release) extractFromRelease(log logrus.FieldLogger, releaseImage, cacheDir, pullSecret string, insecure bool, icspFile string, ocpVersion string) (string, error) {
+	workdir, binary, path, err := r.GetReleaseBinaryPath(releaseImage, cacheDir, ocpVersion)
+	if err != nil {
+		return "", err
+	}
 	log.Infof("extracting %s binary to %s", binary, workdir)
-	err := os.MkdirAll(workdir, 0755)
+	err = os.MkdirAll(workdir, 0755)
 	if err != nil {
 		return "", err
 	}


Test patch:
diff --git a/internal/installercache/installercache_test.go b/internal/installercache/installercache_test.go
index d041d37e321..b0334ddca51 100644
--- a/internal/installercache/installercache_test.go
+++ b/internal/installercache/installercache_test.go
@@ -39,29 +39,29 @@ var _ = Describe("installer cache", func() {
 		os.RemoveAll(cacheDir)
 	})
 
-	testGet := func(releaseID string) (string, string) {
+	testGet := func(releaseID, version string) (string, string) {
 		workdir := filepath.Join(cacheDir, "quay.io", "release-dev")
 		fname := filepath.Join(workdir, releaseID)
 
 		mockRelease.EXPECT().GetReleaseBinaryPath(
-			gomock.Any(), gomock.Any()).
-			Return(workdir, releaseID, fname)
+			gomock.Any(), gomock.Any(), version).
+			Return(workdir, releaseID, fname, nil)
 		mockRelease.EXPECT().Extract(gomock.Any(), releaseID,
-			gomock.Any(), cacheDir, gomock.Any()).
-			DoAndReturn(func(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string) (string, error) {
+			gomock.Any(), cacheDir, gomock.Any(), version).
+			DoAndReturn(func(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string, version string) (string, error) {
 				err := os.WriteFile(fname, []byte("abcde"), 0600)
 				return "", err
 			})
-		l, err := manager.Get(releaseID, "mirror", "pull-secret", mockRelease)
+		l, err := manager.Get(releaseID, "mirror", "pull-secret", mockRelease, version)
 		Expect(err).ShouldNot(HaveOccurred())
 
 		time.Sleep(1 * time.Second)
 		return fname, l.Path
 	}
 	It("evicts the oldest file", func() {
-		r1, l1 := testGet("4.8")
-		r2, l2 := testGet("4.9")
-		r3, l3 := testGet("4.10")
+		r1, l1 := testGet("4.8", "4.8.0")
+		r2, l2 := testGet("4.9", "4.9.0")
+		r3, l3 := testGet("4.10", "4.10.0")
 
 		By("verify that the oldest file was deleted")
 		_, err := os.Stat(r1)
@@ -82,10 +82,10 @@ var _ = Describe("installer cache", func() {
 	})
 
 	It("exising files access time is updated", func() {
-		_, _ = testGet("4.8")
-		r2, _ := testGet("4.9")
-		r1, _ := testGet("4.8")
-		r3, _ := testGet("4.10")
+		_, _ = testGet("4.8", "4.8.0")
+		r2, _ := testGet("4.9", "4.9.0")
+		r1, _ := testGet("4.8", "4.8.0")
+		r3, _ := testGet("4.10", "4.10.0")
 
 		By("verify that the oldest file was deleted")
 		_, err := os.Stat(r1)
@@ -99,9 +99,9 @@ var _ = Describe("installer cache", func() {
 	It("when cache limit is not set eviction is skipped", func() {
 		manager.storageCapacity = 0
 
-		r1, _ := testGet("4.8")
-		r2, _ := testGet("4.9")
-		r3, _ := testGet("4.10")
+		r1, _ := testGet("4.8", "4.8.0")
+		r2, _ := testGet("4.9", "4.9.0")
+		r3, _ := testGet("4.10", "4.10.0")
 
 		By("verify that the no file was deleted")
 		_, err := os.Stat(r1)
@@ -116,38 +116,40 @@ var _ = Describe("installer cache", func() {
 	It("extracts from the mirror", func() {
 		releaseID := "4.10-orig"
 		releaseMirrorID := "4.10-mirror"
+		version := "4.10.0"
 		workdir := filepath.Join(cacheDir, "quay.io", "release-dev")
 		fname := filepath.Join(workdir, releaseID)
 
 		mockRelease.EXPECT().GetReleaseBinaryPath(
-			releaseMirrorID, gomock.Any()).
-			Return(workdir, releaseID, fname)
+			releaseMirrorID, gomock.Any(), version).
+			Return(workdir, releaseID, fname, nil)
 		mockRelease.EXPECT().Extract(gomock.Any(), releaseID,
-			gomock.Any(), cacheDir, gomock.Any()).
-			DoAndReturn(func(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string) (string, error) {
+			gomock.Any(), cacheDir, gomock.Any(), version).
+			DoAndReturn(func(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string, version string) (string, error) {
 				err := os.WriteFile(fname, []byte("abcde"), 0600)
 				return "", err
 			})
-		_, err := manager.Get(releaseID, releaseMirrorID, "pull-secret", mockRelease)
+		_, err := manager.Get(releaseID, releaseMirrorID, "pull-secret", mockRelease, version)
 		Expect(err).ShouldNot(HaveOccurred())
 	})
 
 	It("extracts without a mirror", func() {
 		releaseID := "4.10-orig"
 		releaseMirrorID := ""
+		version := "4.10.0"
 		workdir := filepath.Join(cacheDir, "quay.io", "release-dev")
 		fname := filepath.Join(workdir, releaseID)
 
 		mockRelease.EXPECT().GetReleaseBinaryPath(
-			releaseID, gomock.Any()).
-			Return(workdir, releaseID, fname)
+			releaseID, gomock.Any(), version).
+			Return(workdir, releaseID, fname, nil)
 		mockRelease.EXPECT().Extract(gomock.Any(), releaseID,
-			gomock.Any(), cacheDir, gomock.Any()).
-			DoAndReturn(func(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string) (string, error) {
+			gomock.Any(), cacheDir, gomock.Any(), version).
+			DoAndReturn(func(log logrus.FieldLogger, releaseImage string, releaseImageMirror string, cacheDir string, pullSecret string, version string) (string, error) {
 				err := os.WriteFile(fname, []byte("abcde"), 0600)
 				return "", err
 			})
-		_, err := manager.Get(releaseID, releaseMirrorID, "pull-secret", mockRelease)
+		_, err := manager.Get(releaseID, releaseMirrorID, "pull-secret", mockRelease, version)
 		Expect(err).ShouldNot(HaveOccurred())
 	})
 })
diff --git a/internal/oc/release_test.go b/internal/oc/release_test.go
index ed0f24634bb..89b1903b7bf 100644
--- a/internal/oc/release_test.go
+++ b/internal/oc/release_test.go
@@ -374,7 +374,7 @@ var _ = Describe("oc", func() {
 			args := splitStringToInterfacesArray(command)
 			mockExecuter.EXPECT().Execute(args[0], args[1:]...).Return("", "", 0).Times(1)
 
-			path, err := oc.Extract(log, releaseImage, "", cacheDir, pullSecret)
+			path, err := oc.Extract(log, releaseImage, "", cacheDir, pullSecret, "4.15.0")
 			filePath := filepath.Join(cacheDir+"/"+releaseImage, baremetalInstallBinary)
 			Expect(path).To(Equal(filePath))
 			Expect(err).ShouldNot(HaveOccurred())
@@ -386,14 +386,14 @@ var _ = Describe("oc", func() {
 			args := splitStringToInterfacesArray(command)
 			mockExecuter.EXPECT().Execute(args[0], args[1:]...).Return("", "", 0).Times(1)
 
-			path, err := oc.Extract(log, releaseImage, releaseImageMirror, cacheDir, pullSecret)
+			path, err := oc.Extract(log, releaseImage, releaseImageMirror, cacheDir, pullSecret, "4.15.0")
 			filePath := filepath.Join(cacheDir+"/"+releaseImageMirror, baremetalInstallBinary)
 			Expect(path).To(Equal(filePath))
 			Expect(err).ShouldNot(HaveOccurred())
 		})
 
 		It("extract baremetal-install with no release image or mirror", func() {
-			path, err := oc.Extract(log, "", "", cacheDir, pullSecret)
+			path, err := oc.Extract(log, "", "", cacheDir, pullSecret, "4.15.0")
 			Expect(path).Should(BeEmpty())
 			Expect(err).Should(HaveOccurred())
 		})
@@ -404,7 +404,7 @@ var _ = Describe("oc", func() {
 			mockExecuter.EXPECT().Execute(args[0], args[1:]...).Return("", "Failed to extract the installer", 1).Times(1)
 			mockExecuter.EXPECT().Execute(args[0], args[1:]...).Return("", "", 0).Times(1)
 
-			path, err := oc.Extract(log, releaseImage, "", cacheDir, pullSecret)
+			path, err := oc.Extract(log, releaseImage, "", cacheDir, pullSecret, "4.15.0")
 			filePath := filepath.Join(cacheDir+"/"+releaseImage, baremetalInstallBinary)
 			Expect(path).To(Equal(filePath))
 			Expect(err).ShouldNot(HaveOccurred())
@@ -416,7 +416,7 @@ var _ = Describe("oc", func() {
 			args := splitStringToInterfacesArray(command)
 			mockExecuter.EXPECT().Execute(args[0], args[1:]...).Return("", "Failed to extract the installer", 1).Times(5)
 
-			path, err := oc.Extract(log, releaseImage, "", cacheDir, pullSecret)
+			path, err := oc.Extract(log, releaseImage, "", cacheDir, pullSecret, "4.15.0")
 			Expect(path).To(Equal(""))
 			Expect(err).Should(HaveOccurred())
 		})
@@ -605,6 +605,43 @@ var _ = Describe("getIcspFileFromRegistriesConfig", func() {
 	})
 })
 
+var _ = Describe("GetReleaseBinaryPath", func() {
+	var r Release
+	BeforeEach(func() {
+		r = NewRelease(nil, Config{}, nil)
+	})
+
+	It("returns the openshift-baremetal-install binary for versions earlier than 4.16", func() {
+		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.15.0")
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(bin).To(Equal("openshift-baremetal-install"))
+	})
+
+	It("returns the openshift-install binary for 4.16.0", func() {
+		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0")
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(bin).To(Equal("openshift-install"))
+	})
+
+	It("returns the openshift-install binary for 4.16 pre release", func() {
+		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0-ec.6")
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(bin).To(Equal("openshift-install"))
+		_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.16.0-rc.0")
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(bin).To(Equal("openshift-install"))
+	})
+
+	It("returns the openshift-install binary for versions later than 4.16.0", func() {
+		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.17.0")
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(bin).To(Equal("openshift-install"))
+		_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.18.0")
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(bin).To(Equal("openshift-install"))
+	})
+})
+
 func splitStringToInterfacesArray(str string) []interface{} {
 	argsAsString := strings.Split(str, " ")
 	argsAsInterface := make([]interface{}, len(argsAsString))