Skip to content

Commit 76b44e2

Browse files
Merge pull request #2806 from mabulgu/feature/bmo-dont-require-checksum-for-oci
✨ BMO: do not require checksums for OCI images
2 parents 8fdcff2 + 0d9fc3a commit 76b44e2

File tree

6 files changed

+173
-9
lines changed

6 files changed

+173
-9
lines changed

apis/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/stretchr/testify v1.11.1
77
k8s.io/api v0.34.2
88
k8s.io/apimachinery v0.34.2
9+
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
910
sigs.k8s.io/controller-runtime v0.22.4
1011
)
1112

@@ -26,7 +27,6 @@ require (
2627
gopkg.in/inf.v0 v0.9.1 // indirect
2728
gopkg.in/yaml.v3 v3.0.1 // indirect
2829
k8s.io/klog/v2 v2.130.1 // indirect
29-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
3030
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
3131
sigs.k8s.io/randfill v1.0.0 // indirect
3232
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect

apis/metal3.io/v1alpha1/baremetalhost_types.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ type Image struct {
542542
URL string `json:"url"`
543543

544544
// Checksum is the checksum for the image. Required for all formats
545-
// except for "live-iso".
545+
// except for "live-iso" and OCI images (oci://).
546546
Checksum string `json:"checksum,omitempty"`
547547

548548
// ChecksumType is the checksum algorithm for the image, e.g md5, sha256 or sha512.
@@ -561,6 +561,10 @@ func (image *Image) IsLiveISO() bool {
561561
return image != nil && image.DiskFormat != nil && *image.DiskFormat == "live-iso"
562562
}
563563

564+
func (image *Image) IsOCI() bool {
565+
return image != nil && strings.HasPrefix(image.URL, "oci://")
566+
}
567+
564568
// Custom deploy is a description of a customized deploy process.
565569
type CustomDeploy struct {
566570
// Custom deploy method name.
@@ -1191,7 +1195,11 @@ func (image *Image) GetChecksum() (checksum, checksumType string, err error) {
11911195
return "", "", nil
11921196
}
11931197

1194-
// FIXME(dtantsur): Ironic supports oci:// images with an embedded checksum
1198+
// Checksum is not required for OCI images as they have embedded checksums
1199+
if image.IsOCI() && image.Checksum == "" {
1200+
return "", "", nil
1201+
}
1202+
11951203
if image.Checksum == "" {
11961204
// Return empty if checksum is not provided
11971205
return "", "", errors.New("checksum is required for normal images")

apis/metal3.io/v1alpha1/baremetalhost_types_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/stretchr/testify/assert"
77
corev1 "k8s.io/api/core/v1"
88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/utils/ptr"
910
)
1011

1112
func TestHostNeedsHardwareInspection(t *testing.T) {
@@ -461,6 +462,56 @@ func TestGetImageChecksum(t *testing.T) {
461462
},
462463
Expected: false,
463464
},
465+
{
466+
Scenario: "OCI image without checksum",
467+
Host: BareMetalHost{
468+
ObjectMeta: metav1.ObjectMeta{
469+
Name: "myhost",
470+
Namespace: "myns",
471+
},
472+
Spec: BareMetalHostSpec{
473+
Image: &Image{
474+
URL: "oci://example.com/image:latest",
475+
},
476+
},
477+
},
478+
Expected: true,
479+
ExpectedType: "",
480+
},
481+
{
482+
Scenario: "OCI image with checksum",
483+
Host: BareMetalHost{
484+
ObjectMeta: metav1.ObjectMeta{
485+
Name: "myhost",
486+
Namespace: "myns",
487+
},
488+
Spec: BareMetalHostSpec{
489+
Image: &Image{
490+
URL: "oci://example.com/image:latest",
491+
Checksum: "sha256hash",
492+
},
493+
},
494+
},
495+
Expected: true,
496+
ExpectedType: "",
497+
},
498+
{
499+
Scenario: "live-iso without checksum",
500+
Host: BareMetalHost{
501+
ObjectMeta: metav1.ObjectMeta{
502+
Name: "myhost",
503+
Namespace: "myns",
504+
},
505+
Spec: BareMetalHostSpec{
506+
Image: &Image{
507+
URL: "http://example.com/image.iso",
508+
DiskFormat: ptr.To("live-iso"),
509+
},
510+
},
511+
},
512+
Expected: true,
513+
ExpectedType: "",
514+
},
464515
} {
465516
t.Run(tc.Scenario, func(t *testing.T) {
466517
_, checksumType, actual := tc.Host.Spec.Image.GetChecksum()
@@ -717,3 +768,100 @@ func TestInspectionDisabled(t *testing.T) {
717768
})
718769
}
719770
}
771+
772+
func TestIsOCI(t *testing.T) {
773+
for _, tc := range []struct {
774+
Scenario string
775+
Image *Image
776+
Expected bool
777+
}{
778+
{
779+
Scenario: "nil image",
780+
Image: nil,
781+
Expected: false,
782+
},
783+
{
784+
Scenario: "OCI image",
785+
Image: &Image{
786+
URL: "oci://example.com/image:latest",
787+
},
788+
Expected: true,
789+
},
790+
{
791+
Scenario: "OCI image with checksum",
792+
Image: &Image{
793+
URL: "oci://registry.io/namespace/image:tag",
794+
Checksum: "sha256:abc123",
795+
},
796+
Expected: true,
797+
},
798+
{
799+
Scenario: "HTTP image",
800+
Image: &Image{
801+
URL: "http://example.com/image.qcow2",
802+
},
803+
Expected: false,
804+
},
805+
{
806+
Scenario: "HTTPS image",
807+
Image: &Image{
808+
URL: "https://example.com/image.qcow2",
809+
},
810+
Expected: false,
811+
},
812+
{
813+
Scenario: "empty URL",
814+
Image: &Image{
815+
URL: "",
816+
},
817+
Expected: false,
818+
},
819+
} {
820+
t.Run(tc.Scenario, func(t *testing.T) {
821+
actual := tc.Image.IsOCI()
822+
assert.Equal(t, tc.Expected, actual)
823+
})
824+
}
825+
}
826+
827+
func TestIsLiveISO(t *testing.T) {
828+
for _, tc := range []struct {
829+
Scenario string
830+
Image *Image
831+
Expected bool
832+
}{
833+
{
834+
Scenario: "nil image",
835+
Image: nil,
836+
Expected: false,
837+
},
838+
{
839+
Scenario: "live-iso format",
840+
Image: &Image{
841+
URL: "http://example.com/image.iso",
842+
DiskFormat: ptr.To("live-iso"),
843+
},
844+
Expected: true,
845+
},
846+
{
847+
Scenario: "raw format",
848+
Image: &Image{
849+
URL: "http://example.com/image.qcow2",
850+
DiskFormat: ptr.To("raw"),
851+
},
852+
Expected: false,
853+
},
854+
{
855+
Scenario: "no format specified",
856+
Image: &Image{
857+
URL: "http://example.com/image.qcow2",
858+
},
859+
Expected: false,
860+
},
861+
} {
862+
t.Run(tc.Scenario, func(t *testing.T) {
863+
actual := tc.Image.IsLiveISO()
864+
assert.Equal(t, tc.Expected, actual)
865+
})
866+
}
867+
}

config/base/crds/bases/metal3.io_baremetalhosts.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ spec:
257257
checksum:
258258
description: |-
259259
Checksum is the checksum for the image. Required for all formats
260-
except for "live-iso".
260+
except for "live-iso" and OCI images (oci://).
261261
type: string
262262
checksumType:
263263
description: |-
@@ -993,7 +993,7 @@ spec:
993993
checksum:
994994
description: |-
995995
Checksum is the checksum for the image. Required for all formats
996-
except for "live-iso".
996+
except for "live-iso" and OCI images (oci://).
997997
type: string
998998
checksumType:
999999
description: |-

config/render/capm3.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ spec:
257257
checksum:
258258
description: |-
259259
Checksum is the checksum for the image. Required for all formats
260-
except for "live-iso".
260+
except for "live-iso" and OCI images (oci://).
261261
type: string
262262
checksumType:
263263
description: |-
@@ -993,7 +993,7 @@ spec:
993993
checksum:
994994
description: |-
995995
Checksum is the checksum for the image. Required for all formats
996-
except for "live-iso".
996+
except for "live-iso" and OCI images (oci://).
997997
type: string
998998
checksumType:
999999
description: |-

pkg/provisioner/ironic/ironic.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,12 @@ func (p *ironicProvisioner) setDirectDeployUpdateOptsForNode(ironicNode *nodes.N
616616
"image_disk_format": imageData.DiskFormat,
617617
}
618618

619-
if checksumType == "" {
619+
// For OCI images without checksum, don't set checksum fields
620+
if checksum == "" && checksumType == "" {
621+
optValues["image_checksum"] = nil
622+
optValues["image_os_hash_algo"] = nil
623+
optValues["image_os_hash_value"] = nil
624+
} else if checksumType == "" {
620625
optValues["image_checksum"] = checksum
621626
optValues["image_os_hash_algo"] = nil
622627
optValues["image_os_hash_value"] = nil
@@ -903,7 +908,10 @@ func (p *ironicProvisioner) ironicHasSameImage(ironicNode *nodes.Node, image met
903908
"provisionState", ironicNode.ProvisionState)
904909
} else {
905910
checksum, checksumType, _ := image.GetChecksum()
906-
if checksumType == "" {
911+
// For OCI images without checksum, only compare the URL
912+
if image.IsOCI() && checksum == "" {
913+
sameImage = (ironicNode.InstanceInfo["image_source"] == image.URL)
914+
} else if checksumType == "" {
907915
sameImage = (ironicNode.InstanceInfo["image_source"] == image.URL &&
908916
ironicNode.InstanceInfo["image_checksum"] == checksum &&
909917
ironicNode.InstanceInfo["image_os_hash_algo"] == nil &&

0 commit comments

Comments
 (0)