Closer to something I'm happy with. See diff comments. The commit history is awful, please ignore, will fix. My plan at this point is a bit more integration tests, and to resolve the doc comment.
Diff comments: > diff --git a/curtin/commands/block_meta_v2.py > b/curtin/commands/block_meta_v2.py > index 051649b..9870e58 100644 > --- a/curtin/commands/block_meta_v2.py > +++ b/curtin/commands/block_meta_v2.py > @@ -214,6 +226,67 @@ def _wipe_for_action(action): > return 'superblock' > > > +def verify_format(devpath, storage_config, part_action): > + actions = [v for v in storage_config.values() > + if 'volume' in v and v['volume'] == part_action['id']] > + if not actions: > + return > + fstype = _get_volume_fstype(devpath) > + target_fstype = actions[0]['fstype'] > + msg = ( > + 'Verifying %s format, expecting %s, found %s' % ( > + devpath, fstype, target_fstype)) > + LOG.debug(msg) > + if fstype != target_fstype: > + raise RuntimeError(msg) > + > + > +def partition_verify_sfdisk_v2(part_action, label, sfdisk_part_info, > + storage_config): > + devpath = os.path.realpath(sfdisk_part_info['node']) > + verify_format(devpath, storage_config, part_action) > + if not part_action.get('resize', False): > + verify_size(devpath, int(util.human2bytes(part_action['size'])), > + sfdisk_part_info) > + expected_flag = part_action.get('flag') > + if expected_flag: > + verify_ptable_flag(devpath, expected_flag, label, sfdisk_part_info) > + > + > +def prepare_resize(part_action, storage_config): > + if not part_action.get('resize', False): > + return None > + > + disk_id = part_action['device'] > + disk_action = storage_config[disk_id] > + part_num = part_action['number'] > + disk_dev = disk_action['dev'] > + part_dev = f'{disk_dev}p{part_num}' Done > + > + start_size = get_part_size_bytes(part_dev, part_action) > + end_size = util.human2bytes(part_action['size']) > + > + if end_size == start_size: > + LOG.debug('Skipping resize of %s - partition is already target size', > + part_dev) > + return None > + > + direction = 'up' if start_size < end_size else 'down' > + > + return ResizeOperation(start_size=start_size, end_size=end_size, > + device=part_dev, direction=direction) > + > + > +def process_resizes(resizes, direction): > + for op in resizes: > + if op is not None and direction == op.direction: > + LOG.debug('Resizing %s %s from %s to %s', > + op.device, op.direction, op.start_size, op.end_size) > + util.subp(['e2fsck', '-p', '-f', op.device]) > + end_size_k = op.end_size // 1024 > + util.subp(['resize2fs', op.device, f'{end_size_k}k']) > + > + > def disk_handler_v2(info, storage_config, handlers): > disk_handler_v1(info, storage_config, handlers) > > diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst > index 5fae90f..ab89b16 100644 > --- a/doc/topics/storage.rst > +++ b/doc/topics/storage.rst > @@ -429,6 +429,16 @@ filesystem or be mounted anywhere on the system. > > If the preserve flag is set to true, curtin will verify that the partition > exists and that the ``size`` and ``flag`` match the configuration provided. > +See also the resize flag, which adjust this behavior. > + > +**resize**: *true, false* > + > +Only applicable to v2 storage configuration. > +If the preserve flag is set to false, this value is not applicable. > +If the preserve flag is set to true, curtin will adjust the size of the > +partition to the new size. When adjusting smaller, the size of the contents > +must permit that. When adjusting larger, there must already be a gap beyond > +the partition in question. I think so? Are you thinking of the case where maybe we delete a partition and expand into it? Otherwise I don't understand. If it's just that then I can clarify the wording. > > **name**: *<name>* > > diff --git a/tests/integration/test_block_meta.py > b/tests/integration/test_block_meta.py > index 0c74cd6..dffb5aa 100644 > --- a/tests/integration/test_block_meta.py > +++ b/tests/integration/test_block_meta.py > @@ -415,3 +439,128 @@ class TestBlockMeta(IntegrationTestCase): > ) > finally: > server.stop() > + > + def _do_test_resize(self, start, end): > + start <<= 20 > + end <<= 20 > + img = self.tmp_path('image.img') > + config = StorageConfigBuilder(version=2) > + config.add_image(path=img, size='200M', ptable='gpt') > + p1 = config.add_part(size=start, offset=1 << 20, number=1) > + config.add_format(part=p1) > + self.run_bm(config.render()) > + > + expected = 'preserve my data!' > + with loop_dev(img) as dev: > + self.assertEqual( > + summarize_partitions(dev), [ > + PartData(number=1, offset=1 << 20, size=start), > + ]) > + with self.mount(dev, p1) as mnt_point: > + with open(f'{mnt_point}/data.txt', 'w') as fp: > + fp.write(expected) > + orig_fs_size = _get_filesystem_size(dev, p1) > + self.assertEqual(start, orig_fs_size) > + > + config.set_preserve() > + p1['resize'] = True > + p1['size'] = end > + # self.run_bm(config.render(), fake=True) > + self.run_bm(config.render()) > + > + with loop_dev(img) as dev: > + self.assertEqual( > + summarize_partitions(dev), [ > + PartData(number=1, offset=1 << 20, size=end), > + ]) > + with self.mount(dev, p1) as mnt_point: > + with open(f'{mnt_point}/data.txt', 'r') as fp: > + self.assertEqual(expected, fp.read()) > + resized_fs_size = _get_filesystem_size(dev, p1) > + self.assertEqual(end, resized_fs_size) > + > + def test_resize_up(self): > + self._do_test_resize(40, 80) > + > + def test_resize_down(self): > + self._do_test_resize(80, 40) > + > + def test_sizes(self): > + for size in (1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048): > + size <<= 20 > + img = self.tmp_path('image.img') > + config = StorageConfigBuilder(version=2) > + config.add_image(path=img, size='2100M', ptable='gpt') > + p1 = config.add_part(size=size, offset=1 << 20, number=1) > + config.add_format(part=p1) > + self.run_bm(config.render()) > + > + with loop_dev(img) as dev: > + actual = _get_filesystem_size(dev, p1) > + self.assertEqual(size, actual) > + > + def test_resizes_up(self): > + start = 1 << 20 > + for end in (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048): > + end <<= 20 > + img = self.tmp_path('image.img') > + config = StorageConfigBuilder(version=2) > + config.add_image(path=img, size='2100M', ptable='gpt') > + p1 = config.add_part(size=start, offset=1 << 20, number=1) > + config.add_format(part=p1) > + self.run_bm(config.render()) > + > + with loop_dev(img) as dev: > + self.assertEqual( > + summarize_partitions(dev), [ > + PartData(number=1, offset=1 << 20, size=start), > + ]) > + actual = _get_filesystem_size(dev, p1) > + self.assertEqual(start, actual, 'initial size') > + > + config.set_preserve() > + p1['resize'] = True > + p1['size'] = end > + self.run_bm(config.render()) > + > + with loop_dev(img) as dev: > + self.assertEqual( > + summarize_partitions(dev), [ > + PartData(number=1, offset=1 << 20, size=end), > + ]) > + actual = _get_filesystem_size(dev, p1) > + self.assertEqual(end, actual, 'resized size') > + > + def test_resizes_down(self): > + start = 2048 << 20 > + for end in (128, 256, 512, 1024): > + # resize2fs doesn't want to resize smaller than 128 for a 2048 > + # start > + end <<= 20 > + img = self.tmp_path('image.img') > + config = StorageConfigBuilder(version=2) > + config.add_image(path=img, size='2100M', ptable='gpt') > + p1 = config.add_part(size=start, offset=1 << 20, number=1) > + config.add_format(part=p1) > + self.run_bm(config.render()) > + > + with loop_dev(img) as dev: > + self.assertEqual( > + summarize_partitions(dev), [ > + PartData(number=1, offset=1 << 20, size=start), > + ]) > + actual = _get_filesystem_size(dev, p1) > + self.assertEqual(start, actual, 'initial size') > + > + config.set_preserve() > + p1['resize'] = True > + p1['size'] = end > + self.run_bm(config.render()) > + > + with loop_dev(img) as dev: > + self.assertEqual( > + summarize_partitions(dev), [ > + PartData(number=1, offset=1 << 20, size=end), > + ]) > + actual = _get_filesystem_size(dev, p1) > + self.assertEqual(end, actual, 'resized size') Started this, will do a little more. -- https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/417829 Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:resize into curtin:master. -- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp