From 7bbf7a137b795abd47b93bca778264d8ffb22f4b Mon Sep 17 00:00:00 2001 From: davejbax Date: Sun, 31 Dec 2023 00:08:23 +0000 Subject: [PATCH 1/2] Add repartition method for modifying existing partitions Currently, the 'new' method can be used to effectively manage partitions in an image when the source image does not have the desired layout. However, it relies on a root filesystem archive, which is not available in all cases (e.g. Ubuntu does not publish Raspberry Pi rootfs archives). The 'repartition' method allows for taking an existing image and changing its partitions. That is, partitions may be arbitrarily created, grown, shrunk, or destroyed: this is done according to the partition layout we give in the image config. This adds two new options to partition configuration: * `resize_fs`: Whether to run resize2fs on the partition after repartitioning. This is necessary because: - Not all partitions can be resized with resize2fs (e.g. NTFS) - Only the user knows whether or not a partition from the source image has been resized * `skip_mkfs`: If true, the partition will **not** be formatted with mkfs in repartition mode. --- builder/builder.go | 12 ++++++++ builder/step_mkfs_image.go | 5 +++ builder/step_resize_partition_fs.go | 48 +++++++++++++++++++++++------ config/image_config.go | 6 ++-- config/image_config.hcl2spec.go | 4 +++ 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/builder/builder.go b/builder/builder.go index 376a379a..318275a8 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -149,6 +149,18 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack &StepMountImage{FromKey: "image_loop_device", ResultKey: "image_mountpoint", MountPath: b.config.ImageMountPath}, ) + case "repartition": + steps = append( + steps, + &StepExtractAndCopyImage{FromKey: "rootfs_archive_path"}, + &StepResizeQemuImage{}, + &StepPartitionImage{}, + &StepMapImage{ResultKey: "image_loop_device"}, + &StepMkfsImage{FromKey: "image_loop_device"}, + &StepResizePartitionFs{FromKey: "image_loop_device"}, + &StepMountImage{FromKey: "image_loop_device", ResultKey: "image_mountpoint", MountPath: b.config.ImageMountPath}, + ) + default: return nil, errors.New("invalid build method") } diff --git a/builder/step_mkfs_image.go b/builder/step_mkfs_image.go index 2ef617df..13959c34 100644 --- a/builder/step_mkfs_image.go +++ b/builder/step_mkfs_image.go @@ -22,6 +22,11 @@ func (s *StepMkfsImage) Run(_ context.Context, state multistep.StateBag) multist loopDevice := state.Get(s.FromKey).(string) for i, partition := range config.ImageConfig.ImagePartitions { + if partition.SkipMkfs { + ui.Message(fmt.Sprintf("skipping mkfs for partition #%d", i+1)) + continue + } + cmd := fmt.Sprintf("mkfs.%s", partition.Filesystem) args := append(partition.FilesystemMakeOptions, fmt.Sprintf("%sp%d", loopDevice, i+1)) diff --git a/builder/step_resize_partition_fs.go b/builder/step_resize_partition_fs.go index faff859c..bbd8054f 100644 --- a/builder/step_resize_partition_fs.go +++ b/builder/step_resize_partition_fs.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/packer-plugin-sdk/multistep" "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/mkaczanowski/packer-builder-arm/config" ) // StepResizePartitionFs expand already partitioned image @@ -15,21 +16,48 @@ type StepResizePartitionFs struct { SelectedPartitionKey string } +// Find partitions marked with ResizeFs that we explicitly want to resize +func findPartitionsToResize(imagePartitions []config.Partition) []int { + var selectedPartitions []int + + for i, partition := range imagePartitions { + if partition.ResizeFs { + selectedPartitions = append(selectedPartitions, i+1) + } + } + + return selectedPartitions +} + // Run the step func (s *StepResizePartitionFs) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { var ( - ui = state.Get("ui").(packer.Ui) - - loopDevice = state.Get(s.FromKey).(string) - selectedPartition = state.Get(s.SelectedPartitionKey).(int) - device = fmt.Sprintf("%sp%d", loopDevice, selectedPartition) + ui = state.Get("ui").(packer.Ui) + loopDevice = state.Get(s.FromKey).(string) ) - out, err := exec.Command("resize2fs", "-f", device).CombinedOutput() - ui.Message(fmt.Sprintf("running resize2fs on %s ", device)) - if err != nil { - ui.Error(fmt.Sprintf("error while resizing partition %v: %s", err, out)) - return multistep.ActionHalt + var selectedPartitions []int + + // If we're running in resize mode, we'll have a single selected partition + // to resize from StepExpandPartition, and we can be sure we don't need to + // expand any other partitions. If we're in repartition mode, we manually + // choose which partitions to resize: only the user knows which ones + // have been expanded. + if s.SelectedPartitionKey != "" { + selectedPartitions = append(selectedPartitions, state.Get(s.SelectedPartitionKey).(int)) + } else { + config := state.Get("config").(*Config) + selectedPartitions = findPartitionsToResize(config.ImagePartitions) + } + + for _, partition := range selectedPartitions { + device := fmt.Sprintf("%sp%d", loopDevice, partition) + out, err := exec.Command("resize2fs", "-f", device).CombinedOutput() + ui.Message(fmt.Sprintf("running resize2fs on %s ", device)) + if err != nil { + ui.Error(fmt.Sprintf("error while resizing partition %v: %s", err, out)) + return multistep.ActionHalt + } } return multistep.ActionContinue diff --git a/config/image_config.go b/config/image_config.go index 3ab0e040..849ff8d0 100644 --- a/config/image_config.go +++ b/config/image_config.go @@ -19,6 +19,8 @@ type Partition struct { Filesystem string `mapstructure:"filesystem"` FilesystemMakeOptions []string `mapstructure:"filesystem_make_options"` Mountpoint string `mapstructure:"mountpoint"` + ResizeFs bool `mapstructure:"resize_fs"` + SkipMkfs bool `mapstructure:"skip_mkfs"` } // ChrootMount describes a mountpoint that is being setup @@ -74,8 +76,8 @@ func (c *ImageConfig) Prepare(_ *interpolate.Context) (warnings []string, errs [ errs = append(errs, errors.New("image build method must be specified")) } - if !(c.ImageBuildMethod == "new" || c.ImageBuildMethod == "reuse" || c.ImageBuildMethod == "resize") { - errs = append(errs, errors.New("invalid image build method specified (valid options: new, reuse)")) + if !(c.ImageBuildMethod == "new" || c.ImageBuildMethod == "reuse" || c.ImageBuildMethod == "resize" || c.ImageBuildMethod == "repartition") { + errs = append(errs, errors.New("invalid image build method specified (valid options: new, reuse, resize, repartition)")) } if len(c.ImagePartitions) == 0 { diff --git a/config/image_config.hcl2spec.go b/config/image_config.hcl2spec.go index f44ca7db..c9493e04 100644 --- a/config/image_config.hcl2spec.go +++ b/config/image_config.hcl2spec.go @@ -45,6 +45,8 @@ type FlatPartition struct { Filesystem *string `mapstructure:"filesystem" cty:"filesystem" hcl:"filesystem"` FilesystemMakeOptions []string `mapstructure:"filesystem_make_options" cty:"filesystem_make_options" hcl:"filesystem_make_options"` Mountpoint *string `mapstructure:"mountpoint" cty:"mountpoint" hcl:"mountpoint"` + ResizeFs *bool `mapstructure:"resize_fs" cty:"resize_fs" hcl:"resize_fs"` + SkipMkfs *bool `mapstructure:"skip_mkfs" cty:"skip_mkfs" hcl:"skip_mkfs"` } // FlatMapstructure returns a new FlatPartition. @@ -67,6 +69,8 @@ func (*FlatPartition) HCL2Spec() map[string]hcldec.Spec { "filesystem": &hcldec.AttrSpec{Name: "filesystem", Type: cty.String, Required: false}, "filesystem_make_options": &hcldec.AttrSpec{Name: "filesystem_make_options", Type: cty.List(cty.String), Required: false}, "mountpoint": &hcldec.AttrSpec{Name: "mountpoint", Type: cty.String, Required: false}, + "resize_fs": &hcldec.AttrSpec{Name: "resize_fs", Type: cty.Bool, Required: false}, + "skip_mkfs": &hcldec.AttrSpec{Name: "skip_mkfs", Type: cty.Bool, Required: false}, } return s } From 757a6d7efb755c5f019b66e258c0f12356d8bbac Mon Sep 17 00:00:00 2001 From: davejbax Date: Sun, 31 Dec 2023 00:49:18 +0000 Subject: [PATCH 2/2] Document repartition method --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/README.md b/README.md index 7ff2a73b..2d89c99e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This plugin allows you to build or extend ARM system image. It operates in two m * new - creates empty disk image and populates the rootfs on it * reuse - uses already existing image as the base * resize - uses already existing image but resize given partition (ie. root) +* repartition - uses already existing image but recreates partition table and optionally formats selected partitions Plugin mimics standard image creation process, such as: * building base empty image (dd) @@ -226,6 +227,59 @@ Complete examples: - [`boards/raspberry-pi/raspbian-resize.json`](./boards/raspberry-pi/raspbian-resize.json) - [`boards/beaglebone-black/ubuntu.pkr.hcl`](./boards/beaglebone-black/ubuntu.pkr.hcl) +## Repartitioning image + +The `repartition` method allows for rewriting the partition table of an image, formatting any partitions (opt-out with `skip_mkfs`), and resizing the filesystems on the partition to the size of the partition with `resize2fs`. This is similar in outcome to the `new` method, however may be used with raw image files (`.img` or `.iso`), rather than rootfs archives. + +To repartition an image: + +* Set `image_build_method` to `repartition` +* Ensure all partitions are fully configured in `image_partitions` + * To **prevent** a partition from being reformatted, set `skip_mkfs` to `true` (`false` by default) + * To invoke resize2fs on a partition after the partition table has been rewritten (this is necessary if you change the partition size), set `resize_fs` to `true` (`false` by default). **Note**: as resize2fs only operates on ext{2,3,4} partitions, non-ext partitions cannot be resized. + +For example: + +```json +"builders": [ + { + "type": "arm", + "image_build_method": "resize", + "image_partitions": [ + { + "name": "boot", + "start_sector": "2048", + "size": "524288", // 256MiB + "skip_mkfs": true, + ... + }, + { + "name": "root", + "size": "16777216", // 8GiB + "start_sector": "526336", + "skip_mkfs": true, + "resize_fs": true, + ... + }, + { + "name": "home", + "size": "0", // expand to end of disk + "start_sector": "17303552", + "resize_fs": true, + ... + } + ], + ... + } +] +``` + +Assuming a source image with a boot partition of 256MiB and a root partition of 4GiB, the above configuration will: + +* Keep the boot partition as-is +* Resize the root partition to 8GiB +* Create and format a new partition which fills the remainder of the disk + ## Docker With `artifice` plugin you can pass rootfs archive to docker plugins ```