Skip to content

Commit 14afea8

Browse files
committed
MGMT-24783: Allow getting OS Images by RHCOS version
We're still going to allow getting OS Images by openshift version, but we want to also allow and prioritize getting them by RHCOS version. We also don't want to keep in our storage multiple ISOs that are identical which happen when different openshift versions use the same RHCOS version, so we're going to dedup them before populating the image store.
1 parent a52ca33 commit 14afea8

2 files changed

Lines changed: 195 additions & 88 deletions

File tree

pkg/imagestore/imagestore.go

Lines changed: 108 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ import (
1414
"strings"
1515

1616
"github.com/google/renameio"
17-
"github.com/openshift/assisted-image-service/internal/common"
18-
"github.com/openshift/assisted-image-service/pkg/isoeditor"
1917
"github.com/pkg/errors"
2018
log "github.com/sirupsen/logrus"
2119
"github.com/thoas/go-funk"
2220
"golang.org/x/sync/errgroup"
21+
22+
"github.com/openshift/assisted-image-service/internal/common"
23+
"github.com/openshift/assisted-image-service/pkg/isoeditor"
2324
)
2425

2526
var DefaultVersions = []map[string]string{
@@ -279,12 +280,12 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
279280
return err
280281
}
281282

283+
versions := deduplicateVersions(s.versions)
282284
errs, _ := errgroup.WithContext(ctx)
283285

284-
for i := range s.versions {
285-
imageInfo := s.versions[i]
286+
for i := range versions {
287+
imageInfo := versions[i]
286288
errs.Go(func() error {
287-
openshiftVersion := imageInfo["openshift_version"]
288289
imageVersion := imageInfo["version"]
289290
arch := imageInfo["cpu_architecture"]
290291

@@ -293,7 +294,7 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
293294
return fmt.Errorf("failed to get image type: %v", err)
294295
}
295296

296-
fullPath := filepath.Join(s.dataDir, isoFileName(imageType, openshiftVersion, imageVersion, arch))
297+
fullPath := filepath.Join(s.dataDir, isoFileName(imageType, imageVersion, arch))
297298
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
298299
url := imageInfo["url"]
299300
log.Infof("Downloading iso from %s to %s", url, fullPath)
@@ -302,7 +303,7 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
302303
if err != nil {
303304
return fmt.Errorf("failed to download %s: %v", url, err)
304305
}
305-
log.Infof("Finished downloading for %s-%s (%s)", openshiftVersion, arch, imageVersion)
306+
log.Infof("Finished downloading for %s-%s", imageVersion, arch)
306307
if err := validateISOID(fullPath); err != nil {
307308
message := fmt.Sprintf("failed to validate %s: %v", fullPath, err)
308309
if err = os.Remove(fullPath); err != nil {
@@ -333,8 +334,8 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
333334
return err
334335
}
335336

336-
for i := range s.versions {
337-
imageInfo := s.versions[i]
337+
for i := range versions {
338+
imageInfo := versions[i]
338339
openshiftVersion := imageInfo["openshift_version"]
339340
imageVersion := imageInfo["version"]
340341
arch := imageInfo["cpu_architecture"]
@@ -350,26 +351,26 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
350351
}
351352

352353
// Extract and cache nmstatectl for all architectures
353-
if err := s.extractAndCacheNmstatectl(openshiftVersion, arch); err != nil {
354-
return fmt.Errorf("failed to extract and cache nmstatectl for %s-%s: %v", openshiftVersion, arch, err)
354+
if err := s.extractAndCacheNmstatectl(imageInfo); err != nil {
355+
return fmt.Errorf("failed to extract and cache nmstatectl for %s-%s: %v", imageVersion, arch, err)
355356
}
356357

357358
// Don't attempt to create a minimal ISO for s390x because there's no easy way to edit the kernel parameters
358359
// This means that the rootfs URL can't be added which makes it impossible for us to create a minimal ISO
359360
if arch == "s390x" {
360361
continue
361362
}
362-
minimalPath := filepath.Join(s.dataDir, isoFileName(ImageTypeMinimal, openshiftVersion, imageVersion, arch))
363+
minimalPath := filepath.Join(s.dataDir, isoFileName(ImageTypeMinimal, imageVersion, arch))
363364
if _, err := os.Stat(minimalPath); os.IsNotExist(err) {
364-
log.Infof("Creating minimal iso for %s-%s-%s", openshiftVersion, imageVersion, arch)
365+
log.Infof("Creating minimal iso for %s-%s", imageVersion, arch)
365366

366-
fullPath := filepath.Join(s.dataDir, isoFileName(ImageTypeFull, openshiftVersion, imageVersion, arch))
367-
rootfsURL, err := buildRootfsURL(s.imageServiceBaseURL, arch, openshiftVersion)
367+
fullPath := filepath.Join(s.dataDir, isoFileName(ImageTypeFull, imageVersion, arch))
368+
rootfsURL, err := buildRootfsURL(s.imageServiceBaseURL, arch, imageVersion)
368369
if err != nil {
369370
return fmt.Errorf("failed to build rootfs URL: %v", err)
370371
}
371372

372-
nmstatectlPath, err := s.NmstatectlPathForParams(openshiftVersion, arch)
373+
nmstatectlPath, err := s.NmstatectlPathForParams(imageVersion, arch)
373374
if err != nil {
374375
return err
375376
}
@@ -378,13 +379,55 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
378379
return fmt.Errorf("failed to create minimal iso template for version %s: %v", imageInfo, err)
379380
}
380381

381-
log.Infof("Finished creating minimal iso for %s-%s (%s)", openshiftVersion, arch, imageVersion)
382+
log.Infof("Finished creating minimal iso for %s-%s", imageVersion, arch)
382383
}
383384
}
384385

385386
return nil
386387
}
387388

389+
// deduplicateVersions keeps a single entry per RHCOS version and CPU architecture pair.
390+
// When multiple pairs share the same RHCOS image, the entry with the highest openshift_version is kept.
391+
// It's important to keep that entry because the openshift_version is used to decide if we want the nmstatectl for that entry.
392+
func deduplicateVersions(versions []map[string]string) []map[string]string {
393+
m := make(map[string]map[string]string, len(versions))
394+
395+
for _, entry := range versions {
396+
key := entry["version"] + "@" + entry["cpu_architecture"]
397+
existing, ok := m[key]
398+
if !ok {
399+
m[key] = entry
400+
continue
401+
}
402+
greater, err := common.VersionGreaterOrEqual(entry["openshift_version"], existing["openshift_version"])
403+
if err != nil {
404+
log.Debugf(
405+
"Couldn't check if OS image entry for RHCOS version %s and architecture %s with openshift_version %s has a greater openshift version, keeping %s, error: %v",
406+
entry["version"], entry["cpu_architecture"], entry["openshift_version"], existing["openshift_version"], err,
407+
)
408+
continue
409+
}
410+
if greater {
411+
log.Debugf(
412+
"Replacing duplicate OS image entry for RHCOS version %s and architecture %s with openshift_version %s (was %s)",
413+
entry["version"], entry["cpu_architecture"], entry["openshift_version"], existing["openshift_version"],
414+
)
415+
m[key] = entry
416+
} else {
417+
log.Debugf(
418+
"Skipping duplicate OS image entry for RHCOS version %s and architecture %s (openshift_version %s, keeping %s)",
419+
entry["version"], entry["cpu_architecture"], entry["openshift_version"], existing["openshift_version"],
420+
)
421+
}
422+
}
423+
424+
result := make([]map[string]string, 0, len(m))
425+
for _, entry := range m {
426+
result = append(result, entry)
427+
}
428+
return result
429+
}
430+
388431
func (*rhcosStore) getImageType(imageInfo map[string]string) (string, error) {
389432
validTypes := map[string]bool{
390433
ImageTypeFull: true,
@@ -405,32 +448,36 @@ func (*rhcosStore) getImageType(imageInfo map[string]string) (string, error) {
405448

406449
// extractAndCacheNmstatectl extracts nmstatectl from the rootfs and caches it for later use
407450
// This ensures nmstatectl is available for both minimal ISO creation and PXE provisioning
408-
func (s *rhcosStore) extractAndCacheNmstatectl(openshiftVersion, arch string) error {
451+
func (s *rhcosStore) extractAndCacheNmstatectl(imageInfo map[string]string) error {
452+
openshiftVersion := imageInfo["openshift_version"]
453+
imageVersion := imageInfo["version"]
454+
arch := imageInfo["cpu_architecture"]
455+
409456
// Check if version supports nmstatectl
410457
versionOK, err := common.VersionGreaterOrEqual(openshiftVersion, isoeditor.MinimalVersionForNmstatectl)
411458
if err != nil {
412459
return err
413460
}
414461
if !versionOK {
415-
log.Debugf("Skipping nmstatectl extraction for %s-%s (version does not support nmstatectl)", openshiftVersion, arch)
462+
log.Debugf("Skipping nmstatectl extraction for %s-%s (version does not support nmstatectl)", imageVersion, arch)
416463
return nil
417464
}
418465

419-
nmstatectlPath, err := s.NmstatectlPathForParams(openshiftVersion, arch)
466+
nmstatectlPath, err := s.NmstatectlPathForParams(imageVersion, arch)
420467
if err != nil {
421468
return err
422469
}
423470

424471
// Check if nmstatectl is already cached
425472
if _, err = os.Stat(nmstatectlPath); err == nil {
426-
log.Debugf("nmstatectl already cached for %s-%s", openshiftVersion, arch)
473+
log.Debugf("nmstatectl already cached for %s-%s", imageVersion, arch)
427474
return nil
428475
}
429476

430-
log.Infof("Extracting and caching nmstatectl for %s-%s", openshiftVersion, arch)
477+
log.Infof("Extracting and caching nmstatectl for %s-%s", imageVersion, arch)
431478

432479
// Get the path to the full ISO
433-
fullPath := s.PathForParams(ImageTypeFull, openshiftVersion, arch)
480+
fullPath := s.PathForParams(ImageTypeFull, imageVersion, arch)
434481

435482
// Read rootfs directly from ISO
436483
rootfsFile, err := isoeditor.GetFileFromISO(fullPath, isoeditor.RootfsImagePath)
@@ -475,22 +522,41 @@ func (s *rhcosStore) extractAndCacheNmstatectl(openshiftVersion, arch string) er
475522
return fmt.Errorf("failed to cache nmstatectl: %v", err)
476523
}
477524

478-
log.Infof("Successfully cached nmstatectl for %s-%s", openshiftVersion, arch)
525+
log.Infof("Successfully cached nmstatectl for %s-%s", imageVersion, arch)
479526
return nil
480527
}
481528

482-
func (s *rhcosStore) PathForParams(imageType, openshiftVersion, arch string) string {
483-
var version string
529+
func (s *rhcosStore) findVersionEntry(versionKey, arch string) map[string]string {
484530
for _, entry := range s.versions {
485-
if entry["openshift_version"] == openshiftVersion && entry["cpu_architecture"] == arch {
486-
version = entry["version"]
531+
if entry["cpu_architecture"] != arch {
532+
continue
533+
}
534+
if entry["version"] == versionKey {
535+
return entry
487536
}
488537
}
489-
return filepath.Join(s.dataDir, isoFileName(imageType, openshiftVersion, version, arch))
538+
for _, entry := range s.versions {
539+
if entry["cpu_architecture"] != arch {
540+
continue
541+
}
542+
if entry["openshift_version"] == versionKey {
543+
return entry
544+
}
545+
}
546+
return nil
490547
}
491548

492-
func isoFileName(imageType, openshiftVersion, version, arch string) string {
493-
return fmt.Sprintf("rhcos-%s-%s-%s-%s.iso", imageType, openshiftVersion, version, arch)
549+
func (s *rhcosStore) PathForParams(imageType, versionKey, arch string) string {
550+
rhcosVersion := versionKey
551+
entry := s.findVersionEntry(versionKey, arch)
552+
if entry != nil {
553+
rhcosVersion = entry["version"]
554+
}
555+
return filepath.Join(s.dataDir, isoFileName(imageType, rhcosVersion, arch))
556+
}
557+
558+
func isoFileName(imageType, version, arch string) string {
559+
return fmt.Sprintf("rhcos-%s-%s-%s.iso", imageType, version, arch)
494560
}
495561

496562
func buildRootfsURL(baseURL, arch, version string) (string, error) {
@@ -515,9 +581,9 @@ func (s *rhcosStore) cleanDataDir() error {
515581
var expectedFiles []string
516582
for _, version := range s.versions {
517583
// Only add full isos here as we want to regenerate the minimal image on each deploy
518-
expectedFiles = append(expectedFiles, isoFileName(ImageTypeFull, version["openshift_version"], version["version"], version["cpu_architecture"]))
584+
expectedFiles = append(expectedFiles, isoFileName(ImageTypeFull, version["version"], version["cpu_architecture"]))
519585
// Keep nmstatectl cached files for all architectures (including s390x)
520-
expectedFiles = append(expectedFiles, nmstatectlFileName(version["openshift_version"], version["version"], version["cpu_architecture"]))
586+
expectedFiles = append(expectedFiles, nmstatectlFileName(version["version"], version["cpu_architecture"]))
521587
}
522588

523589
dataDirFiles, err := os.ReadDir(s.dataDir)
@@ -539,24 +605,16 @@ func (s *rhcosStore) cleanDataDir() error {
539605
}
540606

541607
func (s *rhcosStore) HaveVersion(version, arch string) bool {
542-
for _, entry := range s.versions {
543-
v, versionPresent := entry["openshift_version"]
544-
a, archPresent := entry["cpu_architecture"]
545-
if versionPresent && v == version && archPresent && a == arch {
546-
return true
547-
}
548-
}
549-
return false
608+
return s.findVersionEntry(version, arch) != nil
550609
}
551610

552-
func (s *rhcosStore) NmstatectlPathForParams(openshiftVersion, arch string) (string, error) {
553-
var version string
554-
for _, entry := range s.versions {
555-
if entry["openshift_version"] == openshiftVersion && entry["cpu_architecture"] == arch {
556-
version = entry["version"]
557-
}
611+
func (s *rhcosStore) NmstatectlPathForParams(versionKey, arch string) (string, error) {
612+
rhcosVersion := versionKey
613+
entry := s.findVersionEntry(versionKey, arch)
614+
if entry != nil {
615+
rhcosVersion = entry["version"]
558616
}
559-
nmstatectlPath := filepath.Join(s.dataDir, nmstatectlFileName(openshiftVersion, version, arch))
617+
nmstatectlPath := filepath.Join(s.dataDir, nmstatectlFileName(rhcosVersion, arch))
560618

561619
// Safety check: ensures nmstatectlPath stays within the expected dataDir,
562620
// preventing crafted inputs from escaping the intended directory
@@ -566,6 +624,6 @@ func (s *rhcosStore) NmstatectlPathForParams(openshiftVersion, arch string) (str
566624
return nmstatectlPath, nil
567625
}
568626

569-
func nmstatectlFileName(openshiftVersion, version, arch string) string {
570-
return fmt.Sprintf("nmstatectl-%s-%s-%s", openshiftVersion, version, arch)
627+
func nmstatectlFileName(version, arch string) string {
628+
return fmt.Sprintf("nmstatectl-%s-%s", version, arch)
571629
}

0 commit comments

Comments
 (0)