Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image Customizer: Allow omitting disk maxSize and partition start. #10383

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions toolkit/tools/imagecustomizerapi/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/microsoft/azurelinux/toolkit/tools/imagegen/diskutils"
"github.com/microsoft/azurelinux/toolkit/tools/internal/ptrutils"
"github.com/stretchr/testify/assert"
)

Expand All @@ -15,11 +16,11 @@ func TestConfigIsValid(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 3 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(3 * diskutils.MiB)),
Partitions: []Partition{
{
Id: "esp",
Start: 1 * diskutils.MiB,
Start: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
Type: PartitionTypeESP,
},
},
Expand Down Expand Up @@ -52,11 +53,11 @@ func TestConfigIsValidLegacy(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 3 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(3 * diskutils.MiB)),
Partitions: []Partition{
{
Id: "boot",
Start: 1 * diskutils.MiB,
Start: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
Type: PartitionTypeBiosGrub,
},
},
Expand Down Expand Up @@ -84,11 +85,11 @@ func TestConfigIsValidNoBootType(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 2 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(2 * diskutils.MiB)),
Partitions: []Partition{
{
Id: "a",
Start: 1 * diskutils.MiB,
Start: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
},
},
}},
Expand All @@ -109,11 +110,11 @@ func TestConfigIsValidMissingBootLoaderReset(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 3 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(3 * diskutils.MiB)),
Partitions: []Partition{
{
Id: "esp",
Start: 1 * diskutils.MiB,
Start: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
Type: PartitionTypeESP,
},
},
Expand Down Expand Up @@ -145,11 +146,11 @@ func TestConfigIsValidMultipleDisks(t *testing.T) {
Disks: []Disk{
{
PartitionTableType: "gpt",
MaxSize: 1 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
},
{
PartitionTableType: "gpt",
MaxSize: 1 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
},
},
BootType: "legacy",
Expand Down Expand Up @@ -199,7 +200,7 @@ func TestConfigIsValidBadDisk(t *testing.T) {
BootType: BootTypeEfi,
Disks: []Disk{{
PartitionTableType: PartitionTableTypeGpt,
MaxSize: 0,
MaxSize: ptrutils.PtrTo(DiskSize(0)),
}},
},
OS: &OS{
Expand All @@ -218,7 +219,7 @@ func TestConfigIsValidMissingEsp(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 2 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(2 * diskutils.MiB)),
Partitions: []Partition{},
}},
BootType: "efi",
Expand All @@ -239,7 +240,7 @@ func TestConfigIsValidMissingBiosBoot(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 2 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(2 * diskutils.MiB)),
Partitions: []Partition{},
}},
BootType: "legacy",
Expand All @@ -260,11 +261,11 @@ func TestConfigIsValidInvalidMountPoint(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 3 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(3 * diskutils.MiB)),
Partitions: []Partition{
{
Id: "esp",
Start: 1 * diskutils.MiB,
Start: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
Type: PartitionTypeESP,
},
},
Expand Down Expand Up @@ -298,11 +299,11 @@ func TestConfigIsValidKernelCLI(t *testing.T) {
Storage: &Storage{
Disks: []Disk{{
PartitionTableType: "gpt",
MaxSize: 3 * diskutils.MiB,
MaxSize: ptrutils.PtrTo(DiskSize(3 * diskutils.MiB)),
Partitions: []Partition{
{
Id: "esp",
Start: 1 * diskutils.MiB,
Start: ptrutils.PtrTo(DiskSize(1 * diskutils.MiB)),
Type: PartitionTypeESP,
},
},
Expand Down
99 changes: 68 additions & 31 deletions toolkit/tools/imagecustomizerapi/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ package imagecustomizerapi

import (
"fmt"
"sort"

"github.com/microsoft/azurelinux/toolkit/tools/imagegen/diskutils"
"github.com/microsoft/azurelinux/toolkit/tools/internal/ptrutils"
)

const (
Expand All @@ -29,7 +29,8 @@ type Disk struct {
PartitionTableType PartitionTableType `yaml:"partitionTableType"`

// The virtual size of the disk.
MaxSize DiskSize `yaml:"maxSize"`
// Note: This value is filled in by IsValid().
MaxSize *DiskSize `yaml:"maxSize"`

// The partitions to allocate on the disk.
Partitions []Partition `yaml:"partitions"`
Expand All @@ -41,8 +42,10 @@ func (d *Disk) IsValid() error {
return err
}

if d.MaxSize <= 0 {
return fmt.Errorf("a disk's maxSize value (%d) must be a positive non-zero number", d.MaxSize)
if d.MaxSize != nil {
if *d.MaxSize <= 0 {
return fmt.Errorf("a disk's maxSize value (%d) must be a positive non-zero number", *d.MaxSize)
}
}

for i, partition := range d.Partitions {
Expand All @@ -55,23 +58,41 @@ func (d *Disk) IsValid() error {
gptHeaderSize := DiskSize(roundUp(GptHeaderSectorNum*DefaultSectorSize, DefaultPartitionAlignment))
gptFooterSize := DiskSize(roundUp(GptFooterSectorNum*DefaultSectorSize, DefaultPartitionAlignment))

// Check for overlapping partitions.
// First, sort partitions by start index.
sortedPartitions := append([]Partition(nil), d.Partitions...)
sort.Slice(sortedPartitions, func(i, j int) bool {
return sortedPartitions[i].Start < sortedPartitions[j].Start
})
// Auto-fill the start value from the previous partition's end value.
for i := range d.Partitions {
partition := &d.Partitions[i]

if partition.Start == nil {
if i == 0 {
partition.Start = ptrutils.PtrTo(DiskSize(DefaultPartitionAlignment))
} else {
prev := d.Partitions[i-1]
prevEnd, prevHasEnd := prev.GetEnd()
if !prevHasEnd {
return fmt.Errorf("partition (%s) omitted start value but previous partition (%s) has no size or end value",
partition.Id, prev.Id)
}
partition.Start = &prevEnd
}
}

if partition.IsBiosBoot() {
if *partition.Start != diskutils.MiB {
return fmt.Errorf("BIOS boot partition must start at 1 MiB")
}
}
}

// Then, confirm each partition ends before the next starts.
for i := 0; i < len(sortedPartitions)-1; i++ {
a := &sortedPartitions[i]
b := &sortedPartitions[i+1]
// Confirm each partition ends before the next starts.
for i := 0; i < len(d.Partitions)-1; i++ {
a := d.Partitions[i]
b := d.Partitions[i+1]

aEnd, aHasEnd := a.GetEnd()
if !aHasEnd {
return fmt.Errorf("partition (%s) is not last partition but size is set to \"grow\"", a.Id)
}
if aEnd > b.Start {
if aEnd > *b.Start {
bEnd, bHasEnd := b.GetEnd()
bEndStr := ""
if bHasEnd {
Expand All @@ -82,31 +103,47 @@ func (d *Disk) IsValid() error {
}
}

if len(sortedPartitions) > 0 {
if d.MaxSize == nil && len(d.Partitions) <= 0 {
return fmt.Errorf("either disk must specify maxSize or last partition must have an end or size value")
}

if len(d.Partitions) > 0 {
// Make sure the first block isn't used.
firstPartition := sortedPartitions[0]
if firstPartition.Start < gptHeaderSize {
firstPartition := d.Partitions[0]
if *firstPartition.Start < gptHeaderSize {
return fmt.Errorf("invalid partition (%s) start:\nfirst %s of disk is reserved for the GPT header",
firstPartition.Id, gptHeaderSize.HumanReadable())
}

// Check that the disk is big enough for the partition layout.
lastPartition := sortedPartitions[len(sortedPartitions)-1]

// Verify MaxSize value.
lastPartition := d.Partitions[len(d.Partitions)-1]
lastPartitionEnd, lastPartitionHasEnd := lastPartition.GetEnd()

var requiredSize DiskSize
if !lastPartitionHasEnd {
requiredSize = lastPartition.Start + DefaultPartitionAlignment
} else {
requiredSize = lastPartitionEnd
}
switch {
case !lastPartitionHasEnd && d.MaxSize == nil:
return fmt.Errorf("either disk must specify maxSize or last partition (%s) must have an end or size value",
lastPartition.Id)

case d.MaxSize == nil:
// Fill in the disk's size.
diskSize := lastPartitionEnd + gptFooterSize
d.MaxSize = &diskSize

default:
// Check that the disk is big enough for the partition layout.
var requiredSize DiskSize
if !lastPartitionHasEnd {
requiredSize = *lastPartition.Start + DefaultPartitionAlignment
} else {
requiredSize = lastPartitionEnd
}

requiredSize += gptFooterSize
requiredSize += gptFooterSize

if requiredSize > d.MaxSize {
return fmt.Errorf("disk's partitions need %s but maxSize is only %s:\nGPT footer size is %s",
requiredSize.HumanReadable(), d.MaxSize.HumanReadable(), gptFooterSize.HumanReadable())
if requiredSize > *d.MaxSize {
return fmt.Errorf("disk's partitions need %s but maxSize is only %s:\nGPT footer size is %s",
requiredSize.HumanReadable(), d.MaxSize.HumanReadable(), gptFooterSize.HumanReadable())
}
}
}

Expand Down
Loading
Loading