Merge "Add shared image support"

This commit is contained in:
Zuul 2025-04-25 21:19:55 +00:00 committed by Gerrit Code Review
commit c525a16b06
4 changed files with 55 additions and 2 deletions

View File

@ -33,6 +33,13 @@ the Image service for each one as it is generated.
Images from Glance used by Ironic must be flagged as ``public``, which
requires administrative privileges with the Glance image service to set.
.. note::
Starting with the 2025.2 release, the Ironic conductor can access images that
are shared with its project, in addition to those it owns.
To use this feature, ensure the required images are shared with the project
associated with the conductor's credentials.
- For *whole disk images* just upload the image:
.. code-block:: console

View File

@ -22,6 +22,7 @@ from oslo_utils import timeutils
from oslo_utils import uuidutils
from ironic.common import exception
from ironic.common import image_service as service
from ironic.common import keystone
from ironic.conf import CONF
@ -140,10 +141,13 @@ def is_image_available(context, image):
image_visibility = getattr(image, 'visibility', None)
image_owner = getattr(image, 'owner', None)
image_id = getattr(image, 'id', 'unknown')
image_shared_member_list = get_image_member_list(image_id, context)
is_admin = 'admin' in getattr(context, 'roles', [])
project = getattr(context, 'project', 'unknown')
# If an auth token is present and the config allows access via auth token,
# allow image access.
# NOTE(satoshi): This config should be removed in the H (2026.2) cycle
if CONF.allow_image_access_via_auth_token and auth_token:
# We return true here since we want the *user* request context to
# be able to be used.
@ -159,7 +163,11 @@ def is_image_available(context, image):
# allow access.
if image_visibility == 'private' and image_owner == conductor_project_id:
return True
# If the image is shared and the conductor_project_id is in the shared
# member list, allow access
if image_visibility == 'shared'\
and conductor_project_id in image_shared_member_list:
return True
LOG.info(
'Access to %s owned by %s denied to requester %s',
image_id, image_owner, project
@ -198,3 +206,17 @@ def get_conductor_project_id():
except Exception as e:
LOG.debug("Error getting conductor project ID: %s", str(e))
return None
def get_image_member_list(image_id, context):
try:
glance_service = service.GlanceImageService(context=context)
members = glance_service.client.image.members(image_id)
return [
member['member_id']
for member in members
]
except Exception as e:
LOG.error("Unable to retrieve image members for image %s: %s",
image_id, e)
return []

View File

@ -1041,8 +1041,24 @@ class TestIsImageAvailable(base.TestCase):
self.assertTrue(service_utils.is_image_available(
self.context, self.image))
@mock.patch.object(service_utils, 'get_image_member_list', autospec=True)
def test_allow_shared_image_if_member(self, mock_get_members):
self.image.visibility = 'shared'
self.image.id = 'shared-image-id'
self.image.owner = 'some-other-project'
self.context.project = 'test-project'
# Mock the conductor project ID and the shared member list
conductor_id = service_utils.get_conductor_project_id()
mock_get_members.return_value = [conductor_id]
self.assertTrue(service_utils.is_image_available(
self.context, self.image))
mock_get_members.assert_called_once_with('shared-image-id',
self.context)
def test_deny_private_image_different_owner(self):
self.config(allow_image_access_via_auth_token=False)
self.config(ignore_project_check_for_admin_tasks=False)
self.image.visibility = 'private'

View File

@ -0,0 +1,8 @@
---
features:
- |
The Ironic conductor can now access images that are
shared with its project, in addition to those it owns.
To use the feature, ensure the images are shared with
the project associated with the conductor's credentials.