Skip to content

Commit ecd6cba

Browse files
chrngcelvin03
andauthored
Added support for customizable GPT partition index (open-edge-platform#506)
* Added support for customizable partition index * Fixed lint issue * Fixed imagedisc test * Fixed iamgedisc test 2 * Added gdisk in Earthfile * update imagedisc_test.go Signed-off-by: Teoh Suh Haw <suh.haw.teoh@intel.com> * Removed imagedisc_test.backup * Revert "Added gdisk in Earthfile" This reverts commit c9495a7. --------- Signed-off-by: Teoh Suh Haw <suh.haw.teoh@intel.com> Co-authored-by: elvin03 <suh.haw.teoh@intel.com>
1 parent 5265ca3 commit ecd6cba

5 files changed

Lines changed: 153 additions & 57 deletions

File tree

internal/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ type KernelConfig struct {
187187
type PartitionInfo struct {
188188
Name string `yaml:"name"` // Name: label for the partition
189189
ID string `yaml:"id"` // ID: unique identifier for the partition; can be used as a key
190+
Index *int `yaml:"index"` // Index: index for the partition sdx (x = 1, 2, 3, 4, ...)
190191
Flags []string `yaml:"flags"` // Flags: optional flags for the partition (e.g., "boot", "hidden")
191192
Type string `yaml:"type"` // Type: partition type (e.g., "esp", "linux-root-amd64")
192193
TypeGUID string `yaml:"typeUUID"` // TypeGUID: GPT type GUID for the partition (e.g., "8300" for Linux filesystem)

internal/config/config_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"github.com/open-edge-platform/os-image-composer/internal/config/validate"
1010
)
1111

12+
func intPtr(v int) *int { return &v }
13+
1214
func TestMergeStringSlices(t *testing.T) {
1315
defaultSlice := []string{"a", "b", "c"}
1416
userSlice := []string{"c", "d", "e"}
@@ -532,6 +534,7 @@ func TestDiskAndSystemConfigGetters(t *testing.T) {
532534
Partitions: []PartitionInfo{
533535
{
534536
ID: "root",
537+
Index: intPtr(1),
535538
FsType: "ext4",
536539
Start: "1MiB",
537540
End: "0",
@@ -811,6 +814,7 @@ func TestDiskConfigValidation(t *testing.T) {
811814
Partitions: []PartitionInfo{
812815
{
813816
ID: "boot",
817+
Index: intPtr(1),
814818
Name: "EFI Boot",
815819
Type: "esp",
816820
FsType: "fat32",
@@ -821,6 +825,7 @@ func TestDiskConfigValidation(t *testing.T) {
821825
},
822826
{
823827
ID: "root",
828+
Index: intPtr(2),
824829
Name: "Root",
825830
Type: "linux-root-amd64",
826831
FsType: "ext4",
@@ -854,6 +859,7 @@ func TestPartitionInfoFields(t *testing.T) {
854859
Partitions: []PartitionInfo{
855860
{
856861
ID: "efi",
862+
Index: intPtr(1),
857863
Name: "EFI System",
858864
Type: "esp",
859865
TypeGUID: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
@@ -866,6 +872,7 @@ func TestPartitionInfoFields(t *testing.T) {
866872
},
867873
{
868874
ID: "swap",
875+
Index: intPtr(2),
869876
Name: "Swap",
870877
Type: "swap",
871878
TypeGUID: "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F",
@@ -875,6 +882,7 @@ func TestPartitionInfoFields(t *testing.T) {
875882
},
876883
{
877884
ID: "root",
885+
Index: intPtr(3),
878886
Name: "Root",
879887
Type: "linux-root-amd64",
880888
TypeGUID: "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709",
@@ -899,6 +907,9 @@ func TestPartitionInfoFields(t *testing.T) {
899907
if efiPartition.ID != "efi" {
900908
t.Errorf("expected EFI partition ID 'efi', got '%s'", efiPartition.ID)
901909
}
910+
if *efiPartition.Index != 1 {
911+
t.Errorf("expected index 1 for EFI partition, got %d", *efiPartition.Index)
912+
}
902913
if len(efiPartition.Flags) != 2 {
903914
t.Errorf("expected 2 flags for EFI partition, got %d", len(efiPartition.Flags))
904915
}
@@ -920,6 +931,9 @@ func TestPartitionInfoFields(t *testing.T) {
920931
if swapPartition.FsType != "swap" {
921932
t.Errorf("expected swap filesystem type, got '%s'", swapPartition.FsType)
922933
}
934+
if *swapPartition.Index != 2 {
935+
t.Errorf("expected index 2 for swap, got '%d'", *swapPartition.Index)
936+
}
923937
if swapPartition.MountPoint != "" {
924938
t.Errorf("expected empty mount point for swap, got '%s'", swapPartition.MountPoint)
925939
}
@@ -935,6 +949,9 @@ func TestPartitionInfoFields(t *testing.T) {
935949
if rootPartition.MountPoint != "/" {
936950
t.Errorf("expected root mount point '/', got '%s'", rootPartition.MountPoint)
937951
}
952+
if *rootPartition.Index != 3 {
953+
t.Errorf("expected index 3 for root, got '%d'", *rootPartition.Index)
954+
}
938955
if rootPartition.Start != "2GiB" {
939956
t.Errorf("expected root start '2GiB', got '%s'", rootPartition.Start)
940957
}

internal/config/schema/os-image-template.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"type": "object",
121121
"properties": {
122122
"id": { "type": "string", "description": "Partition identifier" },
123+
"index": {"type": "integer", "description": "Partition index"},
123124
"name": { "type": "string", "description": "Partition name/label" },
124125
"type": { "type": "string", "description": "Partition type" },
125126
"typeUUID": { "type": "string", "description": "Partition type UUID" },

internal/image/imagedisc/imagedisc.go

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ func diskPartitionCreate(
482482
log.Errorf("Failed to get disk name from path %s: %v", diskPath, err)
483483
return "", fmt.Errorf("failed to get disk name from path: %s", diskPath)
484484
}
485+
485486
startSector, _ := getSectorOffsetFromSize(diskName, startSizeStr)
486487
var endSector uint64
487488
if partitionInfo.End == "0" {
@@ -505,60 +506,82 @@ func diskPartitionCreate(
505506
log.Infof("Input partition end: " + endSizeStr + ", aligned end sector: " + endSectorStr)
506507

507508
// Create partition
508-
var sfdiskScript strings.Builder
509-
sfdiskScript.WriteString(fmt.Sprintf("start=%d ", startSector))
510-
if endSector != 0 {
511-
size := endSector - startSector
512-
sfdiskScript.WriteString(fmt.Sprintf("size=%d ", size))
513-
}
514-
515-
// Set partition type
509+
// GPT with sgdisk & MBR with sfdisk
516510
if partitionTableType == "gpt" {
517-
// For GPT, use GUID
518511
typeGUID := partitionInfo.TypeGUID
519512
if typeGUID == "" && partitionInfo.Type != "" {
520513
typeGUID, _ = PartitionTypeStrToGUID(partitionInfo.Type)
521514
}
515+
516+
startArg := fmt.Sprintf("%d", startSector)
517+
var endArg string
518+
if endSector == 0 {
519+
endArg = "0"
520+
} else {
521+
endArg = fmt.Sprintf("%d", endSector)
522+
}
523+
524+
// Build sgdisk command: -n (new), -t (type), -c (name)
525+
var parts []string
526+
parts = append(parts, fmt.Sprintf("-n %d:%s:%s", partitionNum, startArg, endArg))
522527
if typeGUID != "" {
523-
sfdiskScript.WriteString(fmt.Sprintf("type=%s ", typeGUID))
528+
parts = append(parts, fmt.Sprintf("-t %d:%s", partitionNum, typeGUID))
524529
}
525-
// Set partition name if provided
526530
if partitionName != "" {
527-
sfdiskScript.WriteString(fmt.Sprintf("name=\"%s\" ", partitionName))
531+
safeName := strings.ReplaceAll(partitionName, "\"", "\\\"")
532+
parts = append(parts, fmt.Sprintf("-c %d:\"%s\"", partitionNum, safeName))
528533
}
534+
535+
cmdStr := fmt.Sprintf("sudo sgdisk %s %s", strings.Join(parts, " "), diskPath)
536+
_, err = shell.ExecCmd(cmdStr, false, shell.HostPath, nil)
537+
if err != nil {
538+
log.Errorf("Failed to create GPT partition %d on disk %s: %v", partitionNum, diskPath, err)
539+
return "", fmt.Errorf("failed to create GPT partition %d on disk %s: %w", partitionNum, diskPath, err)
540+
}
541+
529542
} else {
530-
// For MBR, use hex type code
531-
var typeCode string
532-
switch {
533-
case partitionType == "extended":
534-
typeCode = "5"
535-
case partitionInfo.FsType == "linux-swap":
536-
typeCode = "82"
537-
default:
538-
typeCode = "83" // Linux
543+
var sfdiskScript strings.Builder
544+
sfdiskScript.WriteString(fmt.Sprintf("start=%d ", startSector))
545+
if endSector != 0 {
546+
size := endSector - startSector
547+
sfdiskScript.WriteString(fmt.Sprintf("size=%d ", size))
548+
}
549+
550+
// Set partition type
551+
if partitionTableType == "mbr" {
552+
// For MBR, use hex type code
553+
var typeCode string
554+
switch {
555+
case partitionType == "extended":
556+
typeCode = "5"
557+
case partitionInfo.FsType == "linux-swap":
558+
typeCode = "82"
559+
default:
560+
typeCode = "83" // Linux
561+
}
562+
sfdiskScript.WriteString(fmt.Sprintf("type=%s ", typeCode))
539563
}
540-
sfdiskScript.WriteString(fmt.Sprintf("type=%s ", typeCode))
541-
}
542564

543-
// Handle boot flag
544-
for _, flag := range partitionInfo.Flags {
545-
if flag == "boot" {
546-
sfdiskScript.WriteString("bootable ")
547-
break
565+
// Handle boot flag
566+
for _, flag := range partitionInfo.Flags {
567+
if flag == "boot" {
568+
sfdiskScript.WriteString("bootable ")
569+
break
570+
}
548571
}
549-
}
550572

551-
// Create the partition using sfdisk
552-
cmdStr := fmt.Sprintf("echo '%s' | sudo sfdisk --no-reread --append %s",
553-
sfdiskScript.String(), diskPath)
554-
_, err = shell.ExecCmd(cmdStr, false, shell.HostPath, nil)
555-
if err != nil {
556-
log.Errorf("Failed to create partition %d on disk %s: %v", partitionNum, diskPath, err)
557-
return "", fmt.Errorf("failed to create partition %d on disk %s: %w", partitionNum, diskPath, err)
573+
// Create the partition using sfdisk
574+
cmdStr := fmt.Sprintf("echo '%s' | sudo sfdisk --no-reread --append %s",
575+
sfdiskScript.String(), diskPath)
576+
_, err = shell.ExecCmd(cmdStr, false, shell.HostPath, nil)
577+
if err != nil {
578+
log.Errorf("Failed to create partition %d on disk %s: %v", partitionNum, diskPath, err)
579+
return "", fmt.Errorf("failed to create partition %d on disk %s: %w", partitionNum, diskPath, err)
580+
}
558581
}
559582

560583
// Refresh partition table using partx
561-
cmdStr = fmt.Sprintf("partx -u %s", diskPath)
584+
cmdStr := fmt.Sprintf("partx -u %s", diskPath)
562585
_, err = shell.ExecCmd(cmdStr, true, shell.HostPath, nil)
563586
if err != nil {
564587
log.Errorf("Failed to refresh partition table after creating partition %d: %v", partitionNum, err)
@@ -698,8 +721,33 @@ func DiskPartitionsCreate(diskPath string, partitionsList []config.PartitionInfo
698721
return nil, fmt.Errorf("failed to create GPT partition table on disk %s: %w", diskPath, err)
699722
}
700723

724+
indexPlaceholder := map[int]string{}
725+
for _, p := range partitionsList {
726+
if p.Index != nil {
727+
if *p.Index <= 0 {
728+
return nil, fmt.Errorf("partition %q: index must be > 0 (got %d)", p.ID, *p.Index)
729+
}
730+
if prev, ok := indexPlaceholder[*p.Index]; ok {
731+
return nil, fmt.Errorf("duplicate partition index %d used by %q and %q", *p.Index, prev, p.ID)
732+
}
733+
indexPlaceholder[*p.Index] = p.ID
734+
}
735+
}
736+
737+
var partitionNum int
701738
for i, partitionInfo := range partitionsList {
702-
partitionNum := i + 1
739+
if partitionInfo.Index != nil {
740+
partitionNum = *partitionInfo.Index
741+
} else {
742+
assignedIndex := i + 1
743+
for {
744+
if _, used := indexPlaceholder[assignedIndex]; !used {
745+
break
746+
}
747+
assignedIndex++
748+
}
749+
partitionNum = assignedIndex
750+
}
703751
diskPartDev, err := diskPartitionCreate(diskPath, partitionNum, partitionInfo, partitionTableType, "primary")
704752
if err != nil {
705753
for i := 1; i < partitionNum; i++ {

internal/image/imagedisc/imagedisc_test.go

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/open-edge-platform/os-image-composer/internal/utils/shell"
1111
)
1212

13+
func intPtr(v int) *int { return &v }
14+
1315
func TestIsDigit(t *testing.T) {
1416
tests := []struct {
1517
name string
@@ -785,17 +787,45 @@ func TestDiskPartitionsCreate(t *testing.T) {
785787
End: "100MiB",
786788
FsType: "ext4",
787789
Type: "linux",
790+
Index: intPtr(1),
791+
},
792+
},
793+
partitionTableType: "gpt",
794+
mockCommands: []shell.MockCommand{
795+
{Pattern: ".*fdisk.*sda.*", Output: "Disk /dev/sda: 1 GiB", Error: nil},
796+
{Pattern: ".*label.*gpt.*sfdisk.*", Output: "", Error: nil},
797+
{Pattern: ".*hw_sector_size", Output: "512", Error: nil},
798+
{Pattern: ".*physical_block_size", Output: "4096", Error: nil},
799+
{Pattern: ".*sgdisk.*sda.*", Output: "", Error: nil},
800+
{Pattern: ".*partx.*sda.*", Output: "", Error: nil},
801+
{Pattern: ".*mkfs.*ext4.*sda.*", Output: "", Error: nil},
802+
},
803+
expectError: false,
804+
expectedDevices: 1,
805+
},
806+
{
807+
name: "gpt_partition_index",
808+
diskPath: "/dev/sda",
809+
partitionsList: []config.PartitionInfo{
810+
{
811+
ID: "root",
812+
Name: "root",
813+
Start: "1MiB",
814+
End: "100MiB",
815+
FsType: "ext4",
816+
Type: "linux",
817+
Index: intPtr(14),
788818
},
789819
},
790820
partitionTableType: "gpt",
791821
mockCommands: []shell.MockCommand{
792-
{Pattern: "fdisk -l /dev/sda", Output: "Disk /dev/sda: 1 GiB", Error: nil},
793-
{Pattern: "echo 'label: gpt'", Output: "", Error: nil},
794-
{Pattern: "cat /sys/block/sda/queue/hw_sector_size", Output: "512", Error: nil},
795-
{Pattern: "cat /sys/block/sda/queue/physical_block_size", Output: "4096", Error: nil},
796-
{Pattern: "echo", Output: "", Error: nil},
797-
{Pattern: "partx -u /dev/sda", Output: "", Error: nil},
798-
{Pattern: "mkfs", Output: "", Error: nil},
822+
{Pattern: ".*fdisk.*sda.*", Output: "Disk /dev/sda: 1 GiB", Error: nil},
823+
{Pattern: ".*label.*gpt.*sfdisk.*", Output: "", Error: nil},
824+
{Pattern: ".*hw_sector_size", Output: "512", Error: nil},
825+
{Pattern: ".*physical_block_size", Output: "4096", Error: nil},
826+
{Pattern: ".*sgdisk.*sda.*", Output: "", Error: nil},
827+
{Pattern: ".*partx.*sda.*", Output: "", Error: nil},
828+
{Pattern: ".*mkfs.*ext4.*sda.*", Output: "", Error: nil},
799829
},
800830
expectError: false,
801831
expectedDevices: 1,
@@ -814,13 +844,13 @@ func TestDiskPartitionsCreate(t *testing.T) {
814844
},
815845
partitionTableType: "mbr",
816846
mockCommands: []shell.MockCommand{
817-
{Pattern: "fdisk -l /dev/sda", Output: "Disk /dev/sda: 1 GiB", Error: nil},
818-
{Pattern: "echo 'label: dos'", Output: "", Error: nil},
819-
{Pattern: "cat /sys/block/sda/queue/hw_sector_size", Output: "512", Error: nil},
820-
{Pattern: "cat /sys/block/sda/queue/physical_block_size", Output: "4096", Error: nil},
821-
{Pattern: "echo", Output: "", Error: nil},
822-
{Pattern: "partx -u /dev/sda", Output: "", Error: nil},
823-
{Pattern: "mkfs", Output: "", Error: nil},
847+
{Pattern: ".*fdisk.*sda.*", Output: "Disk /dev/sda: 1 GiB", Error: nil},
848+
{Pattern: ".*label.*dos.*sfdisk.*", Output: "", Error: nil},
849+
{Pattern: ".*hw_sector_size", Output: "512", Error: nil},
850+
{Pattern: ".*physical_block_size", Output: "4096", Error: nil},
851+
{Pattern: ".*sfdisk.*append.*sda.*", Output: "", Error: nil},
852+
{Pattern: ".*partx.*sda.*", Output: "", Error: nil},
853+
{Pattern: ".*mkfs.*ext4.*sda.*", Output: "", Error: nil},
824854
},
825855
expectError: false,
826856
expectedDevices: 1,
@@ -838,11 +868,10 @@ func TestDiskPartitionsCreate(t *testing.T) {
838868
},
839869
partitionTableType: "gpt",
840870
mockCommands: []shell.MockCommand{
841-
{Pattern: "fdisk -l /dev/sda", Output: "Disk /dev/sda: 1 GiB", Error: nil},
842-
{Pattern: "echo 'label: gpt'", Output: "", Error: nil},
843-
{Pattern: "cat /sys/block/sda/queue/hw_sector_size", Output: "512", Error: nil},
844-
{Pattern: "cat /sys/block/sda/queue/physical_block_size", Output: "4096", Error: nil},
845-
{Pattern: "echo", Output: "", Error: fmt.Errorf("sfdisk failed")},
871+
{Pattern: ".*fdisk.*sda.*", Output: "Disk /dev/sda: 1 GiB", Error: nil},
872+
{Pattern: ".*label.*gpt.*sfdisk.*", Output: "", Error: fmt.Errorf("sgdisk failed")},
873+
{Pattern: ".*hw_sector_size", Output: "512", Error: nil},
874+
{Pattern: ".*physical_block_size", Output: "4096", Error: nil},
846875
},
847876
expectError: true,
848877
errorMsg: "failed to create partition",

0 commit comments

Comments
 (0)