Merge "Don't create a hardlink to a symlink when handling file:// URLs"

This commit is contained in:
Zuul 2024-01-20 00:23:33 +00:00 committed by Gerrit Code Review
commit a42f23f475
3 changed files with 37 additions and 4 deletions

View File

@ -322,13 +322,21 @@ class FileImageService(BaseImageService):
image_file.close()
os.remove(dest_image_path)
# NOTE(dtantsur): os.link is supposed to follow symlinks, but it
# does not: https://github.com/python/cpython/issues/81793
real_image_path = os.path.realpath(source_image_path)
try:
os.link(source_image_path, dest_image_path)
os.link(real_image_path, dest_image_path)
except OSError as exc:
LOG.debug('Could not create a link from %(src)s to %(dest)s, '
'will copy the content instead. Error: %(exc)s.',
orig = (f' (real path {real_image_path})'
if real_image_path != source_image_path
else '')
LOG.debug('Could not create a link from %(src)s%(orig)s to '
'%(dest)s, will copy the content instead. '
'Error: %(exc)s.',
{'src': source_image_path, 'dest': dest_image_path,
'exc': exc})
'orig': orig, 'exc': exc})
else:
return

View File

@ -612,6 +612,7 @@ class FileImageServiceTestCase(base.TestCase):
@mock.patch.object(shutil, 'copyfile', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os.path, 'realpath', lambda p: p)
@mock.patch.object(os, 'remove', autospec=True)
@mock.patch.object(image_service.FileImageService, 'validate_href',
autospec=True)
@ -642,6 +643,24 @@ class FileImageServiceTestCase(base.TestCase):
link_mock.assert_called_once_with(self.href_path, 'file')
copy_mock.assert_called_once_with(self.href_path, 'file')
@mock.patch.object(shutil, 'copyfile', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os.path, 'realpath', autospec=True)
@mock.patch.object(os, 'remove', autospec=True)
@mock.patch.object(image_service.FileImageService, 'validate_href',
autospec=True)
def test_download_symlink(self, _validate_mock, remove_mock,
realpath_mock, link_mock, copy_mock):
_validate_mock.return_value = self.href_path
realpath_mock.side_effect = lambda p: p + '.real'
file_mock = mock.MagicMock(spec=io.BytesIO)
file_mock.name = 'file'
self.service.download(self.href, file_mock)
_validate_mock.assert_called_once_with(mock.ANY, self.href)
realpath_mock.assert_called_once_with(self.href_path)
link_mock.assert_called_once_with(self.href_path + '.real', 'file')
copy_mock.assert_not_called()
@mock.patch.object(shutil, 'copyfile', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'remove', autospec=True)

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fixes the behavior of ``file:///`` image URLs pointing at a symlink.
Ironic no longer creates a hard link to the symlink, which could cause
confusing FileNotFoundError to happen if the symlink is relative.