diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py
index 7584d896..eb57fb99 100644
--- a/gvm/protocols/gmp/_gmpnext.py
+++ b/gvm/protocols/gmp/_gmpnext.py
@@ -8,7 +8,7 @@
from .._protocol import T
from ._gmp227 import GMPv227
-from .requests.next import AgentGroups, AgentInstallers, Agents
+from .requests.next import AgentGroups, AgentInstallers, Agents, OCIImageTargets
class GMPNext(GMPv227[T]):
@@ -274,3 +274,121 @@ def clone_agent_group(
return self._send_request_and_transform_response(
AgentGroups.clone_agent_group(agent_group_id)
)
+
+ def create_oci_image_target(
+ self,
+ name: str,
+ image_references: list[str],
+ *,
+ comment: Optional[str] = None,
+ credential_id: Optional[EntityID] = None,
+ ) -> T:
+ """Create a new OCI image target
+
+ Args:
+ name: Name of the OCI image target
+ image_references: List of OCI image URLs to scan
+ comment: Comment for the target
+ credential_id: UUID of a credential to use on target
+ """
+ return self._send_request_and_transform_response(
+ OCIImageTargets.create_oci_image_target(
+ name=name,
+ image_references=image_references,
+ comment=comment,
+ credential_id=credential_id,
+ )
+ )
+
+ def modify_oci_image_target(
+ self,
+ oci_image_target_id: EntityID,
+ *,
+ name: Optional[str] = None,
+ comment: Optional[str] = None,
+ image_references: Optional[list[str]] = None,
+ credential_id: Optional[EntityID] = None,
+ ) -> T:
+ """Modify an existing OCI image target.
+
+ Args:
+ oci_image_target_id: UUID of target to modify.
+ comment: Comment on target.
+ name: Name of target.
+ image_references: List of OCI image URLs to scan.
+ credential_id: UUID of credential to use on target.
+ """
+ return self._send_request_and_transform_response(
+ OCIImageTargets.modify_oci_image_target(
+ oci_image_target_id,
+ name=name,
+ comment=comment,
+ image_references=image_references,
+ credential_id=credential_id,
+ )
+ )
+
+ def clone_oci_image_target(self, oci_image_target_id: EntityID) -> T:
+ """Clone an existing OCI image target.
+
+ Args:
+ oci_image_target_id: UUID of an existing OCI image target to clone.
+ """
+ return self._send_request_and_transform_response(
+ OCIImageTargets.clone_oci_image_target(oci_image_target_id)
+ )
+
+ def delete_oci_image_target(
+ self, oci_image_target_id: EntityID, *, ultimate: Optional[bool] = False
+ ) -> T:
+ """Delete an existing OCI image target.
+
+ Args:
+ oci_image_target_id: UUID of an existing OCI image target to delete.
+ ultimate: Whether to remove entirely or to the trashcan.
+ """
+ return self._send_request_and_transform_response(
+ OCIImageTargets.delete_oci_image_target(
+ oci_image_target_id, ultimate=ultimate
+ )
+ )
+
+ def get_oci_image_target(
+ self, oci_image_target_id: EntityID, *, tasks: Optional[bool] = None
+ ) -> T:
+ """Request a single OCI image target.
+
+ Args:
+ oci_image_target_id: UUID of the OCI image target to request.
+ tasks: Whether to include list of tasks that use the target
+ """
+ return self._send_request_and_transform_response(
+ OCIImageTargets.get_oci_image_target(
+ oci_image_target_id, tasks=tasks
+ )
+ )
+
+ def get_oci_image_targets(
+ self,
+ *,
+ filter_string: Optional[str] = None,
+ filter_id: Optional[EntityID] = None,
+ trash: Optional[bool] = None,
+ tasks: Optional[bool] = None,
+ ) -> T:
+ """Request a list of OCI image targets.
+
+ Args:
+ filter_string: Filter term to use for the query.
+ filter_id: UUID of an existing filter to use for the query.
+ trash: Whether to include targets in the trashcan.
+ tasks: Whether to include list of tasks that use the target.
+ """
+ return self._send_request_and_transform_response(
+ OCIImageTargets.get_oci_image_targets(
+ filter_string=filter_string,
+ filter_id=filter_id,
+ trash=trash,
+ tasks=tasks,
+ )
+ )
diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py
index b42ae874..2d1e7184 100644
--- a/gvm/protocols/gmp/requests/next/__init__.py
+++ b/gvm/protocols/gmp/requests/next/__init__.py
@@ -5,6 +5,7 @@
from gvm.protocols.gmp.requests.next._agent_groups import AgentGroups
from gvm.protocols.gmp.requests.next._agent_installers import AgentInstallers
from gvm.protocols.gmp.requests.next._agents import Agents
+from gvm.protocols.gmp.requests.next._oci_image_targets import OCIImageTargets
from .._entity_id import EntityID
from .._version import Version
@@ -113,6 +114,7 @@
"InfoType",
"Notes",
"Nvts",
+ "OCIImageTargets",
"OperatingSystems",
"Overrides",
"Permissions",
diff --git a/gvm/protocols/gmp/requests/next/_oci_image_targets.py b/gvm/protocols/gmp/requests/next/_oci_image_targets.py
new file mode 100644
index 00000000..9605e2b0
--- /dev/null
+++ b/gvm/protocols/gmp/requests/next/_oci_image_targets.py
@@ -0,0 +1,186 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from typing import Optional
+
+from gvm.errors import RequiredArgument
+from gvm.protocols.core import Request
+from gvm.protocols.gmp.requests._entity_id import EntityID
+from gvm.utils import to_bool, to_comma_list
+from gvm.xml import XmlCommand
+
+
+class OCIImageTargets:
+ @classmethod
+ def create_oci_image_target(
+ cls,
+ name: str,
+ image_references: list[str],
+ *,
+ comment: Optional[str] = None,
+ credential_id: Optional[EntityID] = None,
+ ) -> Request:
+ """Create a new OCI image target
+
+ Args:
+ name: Name of the target
+ image_references: List of OCI image URLs to scan
+ comment: Comment for the target
+ credential_id: UUID of a credential to use on target
+ """
+ if not name:
+ raise RequiredArgument(
+ function=cls.create_oci_image_target.__name__, argument="name"
+ )
+
+ if not image_references:
+ raise RequiredArgument(
+ function=cls.create_oci_image_target.__name__,
+ argument="image_references",
+ )
+
+ cmd = XmlCommand("create_oci_image_target")
+ cmd.add_element("name", name)
+ cmd.add_element("image_references", to_comma_list(image_references))
+
+ if comment:
+ cmd.add_element("comment", comment)
+
+ if credential_id:
+ cmd.add_element("credential", attrs={"id": str(credential_id)})
+
+ return cmd
+
+ @classmethod
+ def modify_oci_image_target(
+ cls,
+ oci_image_target_id: EntityID,
+ *,
+ name: Optional[str] = None,
+ comment: Optional[str] = None,
+ image_references: Optional[list[str]] = None,
+ credential_id: Optional[EntityID] = None,
+ ) -> Request:
+ """Modify an existing target.
+
+ Args:
+ oci_image_target_id: UUID of target to modify.
+ comment: Comment on target.
+ name: Name of target.
+ image_references: List of OCI image URLs.
+ credential_id: UUID of credential to use on target.
+ """
+ if not oci_image_target_id:
+ raise RequiredArgument(
+ function=cls.modify_oci_image_target.__name__,
+ argument="oci_image_target_id",
+ )
+
+ cmd = XmlCommand("modify_oci_image_target")
+ cmd.set_attribute("oci_image_target_id", str(oci_image_target_id))
+
+ if comment:
+ cmd.add_element("comment", comment)
+
+ if name:
+ cmd.add_element("name", name)
+
+ if image_references:
+ cmd.add_element("image_references", to_comma_list(image_references))
+
+ if credential_id:
+ cmd.add_element("credential", attrs={"id": str(credential_id)})
+
+ return cmd
+
+ @classmethod
+ def clone_oci_image_target(cls, oci_image_target_id: EntityID) -> Request:
+ """Clone an existing OCI image target.
+
+ Args:
+ oci_image_target_id: UUID of an existing target to clone.
+ """
+ if not oci_image_target_id:
+ raise RequiredArgument(
+ function=cls.clone_oci_image_target.__name__,
+ argument="oci_image_target_id",
+ )
+
+ cmd = XmlCommand("create_oci_image_target")
+ cmd.add_element("copy", str(oci_image_target_id))
+ return cmd
+
+ @classmethod
+ def delete_oci_image_target(
+ cls, oci_image_target_id: EntityID, *, ultimate: Optional[bool] = False
+ ) -> Request:
+ """Delete an existing OCI image target.
+
+ Args:
+ oci_image_target_id: UUID of an existing target to delete.
+ ultimate: Whether to remove entirely or to the trashcan.
+ """
+ if not oci_image_target_id:
+ raise RequiredArgument(
+ function=cls.delete_oci_image_target.__name__,
+ argument="oci_image_target_id",
+ )
+
+ cmd = XmlCommand("delete_oci_image_target")
+ cmd.set_attribute("oci_image_target_id", str(oci_image_target_id))
+ cmd.set_attribute("ultimate", to_bool(ultimate))
+ return cmd
+
+ @classmethod
+ def get_oci_image_target(
+ cls, oci_image_target_id: EntityID, *, tasks: Optional[bool] = None
+ ) -> Request:
+ """Request a single OCI Image target.
+
+ Args:
+ oci_image_target_id: UUID of the target to request.
+ tasks: Whether to include list of tasks that use the target
+ """
+ if not oci_image_target_id:
+ raise RequiredArgument(
+ function=cls.get_oci_image_target.__name__,
+ argument="oci_image_target_id",
+ )
+
+ cmd = XmlCommand("get_oci_image_targets")
+ cmd.set_attribute("oci_image_target_id", str(oci_image_target_id))
+
+ if tasks is not None:
+ cmd.set_attribute("tasks", to_bool(tasks))
+
+ return cmd
+
+ @classmethod
+ def get_oci_image_targets(
+ cls,
+ *,
+ filter_string: Optional[str] = None,
+ filter_id: Optional[EntityID] = None,
+ trash: Optional[bool] = None,
+ tasks: Optional[bool] = None,
+ ) -> Request:
+ """Request a list of OCI image targets.
+
+ Args:
+ filter_string: Filter term to use for the query.
+ filter_id: UUID of an existing filter to use for the query.
+ trash: Whether to include targets in the trashcan.
+ tasks: Whether to include list of tasks that use the target.
+ """
+ cmd = XmlCommand("get_oci_image_targets")
+ cmd.add_filter(filter_string, filter_id)
+
+ if trash is not None:
+ cmd.set_attribute("trash", to_bool(trash))
+
+ if tasks is not None:
+ cmd.set_attribute("tasks", to_bool(tasks))
+
+ return cmd
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/__init__.py b/tests/protocols/gmpnext/entities/oci_image_targets/__init__.py
new file mode 100644
index 00000000..4f8337bf
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/__init__.py
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from .test_clone_oci_image_target import GmpCloneOCIImageTargetTestMixin
+from .test_create_oci_image_target import GmpCreateOCIImageTargetTestMixin
+from .test_delete_oci_image_target import GmpDeleteOCIImageTargetTestMixin
+from .test_get_oci_image_target import GmpGetOCIImageTargetTestMixin
+from .test_get_oci_image_targets import GmpGetOCIImageTargetsTestMixin
+from .test_modify_oci_image_target import GmpModifyOCIImageTargetTestMixin
+
+__all__ = (
+ "GmpCloneOCIImageTargetTestMixin",
+ "GmpCreateOCIImageTargetTestMixin",
+ "GmpDeleteOCIImageTargetTestMixin",
+ "GmpGetOCIImageTargetTestMixin",
+ "GmpGetOCIImageTargetsTestMixin",
+ "GmpModifyOCIImageTargetTestMixin",
+)
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/test_clone_oci_image_target.py b/tests/protocols/gmpnext/entities/oci_image_targets/test_clone_oci_image_target.py
new file mode 100644
index 00000000..1ae8666e
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/test_clone_oci_image_target.py
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from gvm.errors import RequiredArgument
+
+
+class GmpCloneOCIImageTargetTestMixin:
+ TARGET_ID = "00000000-0000-0000-0000-000000000000"
+
+ def test_clone(self):
+ self.gmp.clone_oci_image_target(self.TARGET_ID)
+
+ self.connection.send.has_been_called_with(
+ ""
+ f"{self.TARGET_ID}"
+ "".encode("utf-8")
+ )
+
+ def test_missing_id(self):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.clone_oci_image_target("")
+
+ with self.assertRaises(RequiredArgument):
+ self.gmp.clone_oci_image_target(None)
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/test_create_oci_image_target.py b/tests/protocols/gmpnext/entities/oci_image_targets/test_create_oci_image_target.py
new file mode 100644
index 00000000..c22346de
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/test_create_oci_image_target.py
@@ -0,0 +1,54 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from gvm.errors import RequiredArgument
+
+
+class GmpCreateOCIImageTargetTestMixin:
+ def test_create_target_missing_name(self):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.create_oci_image_target(
+ None, image_references=["oci:foo/bar:latest"]
+ )
+
+ with self.assertRaises(RequiredArgument):
+ self.gmp.create_oci_image_target(
+ name=None, image_references=["oci:foo/bar:latest"]
+ )
+
+ with self.assertRaises(RequiredArgument):
+ self.gmp.create_oci_image_target(
+ "", image_references=["oci:foo/bar:latest"]
+ )
+
+ def test_create_target_missing_image_references(self):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.create_oci_image_target(name="foo", image_references=None)
+
+ def test_create_target_with_comment(self):
+ self.gmp.create_oci_image_target(
+ "foo", image_references=["oci:foo/bar:latest"], comment="bar"
+ )
+
+ self.connection.send.has_been_called_with(
+ b""
+ b"foo"
+ b"oci:foo/bar:latest"
+ b"bar"
+ b""
+ )
+
+ def test_create_target_with_smb_credential_id(self):
+ self.gmp.create_oci_image_target(
+ "foo", image_references=["oci:foo/bar:latest"], credential_id="c1"
+ )
+
+ self.connection.send.has_been_called_with(
+ b""
+ b"foo"
+ b"oci:foo/bar:latest"
+ b''
+ b""
+ )
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/test_delete_oci_image_target.py b/tests/protocols/gmpnext/entities/oci_image_targets/test_delete_oci_image_target.py
new file mode 100644
index 00000000..250e399b
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/test_delete_oci_image_target.py
@@ -0,0 +1,29 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from gvm.errors import GvmError
+
+
+class GmpDeleteOCIImageTargetTestMixin:
+ def test_delete(self):
+ self.gmp.delete_oci_image_target("a1")
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_delete_ultimate(self):
+ self.gmp.delete_oci_image_target("a1", ultimate=True)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_missing_id(self):
+ with self.assertRaises(GvmError):
+ self.gmp.delete_oci_image_target(None)
+
+ with self.assertRaises(GvmError):
+ self.gmp.delete_oci_image_target("")
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/test_get_oci_image_target.py b/tests/protocols/gmpnext/entities/oci_image_targets/test_get_oci_image_target.py
new file mode 100644
index 00000000..766d1803
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/test_get_oci_image_target.py
@@ -0,0 +1,41 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from gvm.errors import RequiredArgument
+
+
+class GmpGetOCIImageTargetTestMixin:
+ def test_get_target(self):
+ self.gmp.get_oci_image_target("t1")
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ self.gmp.get_oci_image_target(oci_image_target_id="t1")
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_get_target_missing_target_id(self):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.get_oci_image_target(oci_image_target_id=None)
+
+ with self.assertRaises(RequiredArgument):
+ self.gmp.get_oci_image_target("")
+
+ def test_get_target_with_tasks(self):
+ self.gmp.get_oci_image_target(oci_image_target_id="t1", tasks=True)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ self.gmp.get_oci_image_target(oci_image_target_id="t1", tasks=False)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/test_get_oci_image_targets.py b/tests/protocols/gmpnext/entities/oci_image_targets/test_get_oci_image_targets.py
new file mode 100644
index 00000000..4cf22336
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/test_get_oci_image_targets.py
@@ -0,0 +1,51 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+
+class GmpGetOCIImageTargetsTestMixin:
+ def test_get_targets(self):
+ self.gmp.get_oci_image_targets()
+
+ self.connection.send.has_been_called_with(b"")
+
+ def test_get_targets_with_filter_string(self):
+ self.gmp.get_oci_image_targets(filter_string="foo=bar")
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_get_targets_with_filter_id(self):
+ self.gmp.get_oci_image_targets(filter_id="f1")
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_get_targets_with_trash(self):
+ self.gmp.get_oci_image_targets(trash=True)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ self.gmp.get_oci_image_targets(trash=False)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_get_targets_with_tasks(self):
+ self.gmp.get_oci_image_targets(tasks=True)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ self.gmp.get_oci_image_targets(tasks=False)
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
diff --git a/tests/protocols/gmpnext/entities/oci_image_targets/test_modify_oci_image_target.py b/tests/protocols/gmpnext/entities/oci_image_targets/test_modify_oci_image_target.py
new file mode 100644
index 00000000..322e6abb
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/oci_image_targets/test_modify_oci_image_target.py
@@ -0,0 +1,75 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from gvm.errors import RequiredArgument
+
+
+class GmpModifyOCIImageTargetTestMixin:
+ def test_modify_oci_image_target(self):
+ self.gmp.modify_oci_image_target(oci_image_target_id="t1")
+
+ self.connection.send.has_been_called_with(
+ b''
+ )
+
+ def test_modify_target_missing_target_id(self):
+ with self.assertRaises(RequiredArgument):
+ self.gmp.modify_oci_image_target(oci_image_target_id=None)
+
+ with self.assertRaises(RequiredArgument):
+ self.gmp.modify_oci_image_target(oci_image_target_id="")
+
+ def test_modify_target_with_comment(self):
+ self.gmp.modify_oci_image_target(
+ oci_image_target_id="t1", comment="foo"
+ )
+
+ self.connection.send.has_been_called_with(
+ b''
+ b"foo"
+ b""
+ )
+
+ def test_modify_target_with_image_references(self):
+ self.gmp.modify_oci_image_target(
+ oci_image_target_id="t1", image_references=["oci://foo/bar:latest"]
+ )
+
+ self.connection.send.has_been_called_with(
+ b''
+ b"oci://foo/bar:latest"
+ b""
+ )
+
+ self.gmp.modify_oci_image_target(
+ oci_image_target_id="t1",
+ image_references=["oci://foo/bar:latest", "oci://baz/qux:latest"],
+ )
+
+ self.connection.send.has_been_called_with(
+ b''
+ b"oci://foo/bar:latest,oci://baz/qux:latest"
+ b""
+ )
+
+ def test_modify_target_with_name(self):
+ self.gmp.modify_oci_image_target(oci_image_target_id="t1", name="foo")
+
+ self.connection.send.has_been_called_with(
+ b''
+ b"foo"
+ b""
+ )
+
+ def test_modify_target_with_smb_credential_id(self):
+ self.gmp.modify_oci_image_target(
+ oci_image_target_id="t1", credential_id="c1"
+ )
+
+ self.connection.send.has_been_called_with(
+ b''
+ b''
+ b""
+ )
diff --git a/tests/protocols/gmpnext/entities/test_oci_image_targets.py b/tests/protocols/gmpnext/entities/test_oci_image_targets.py
new file mode 100644
index 00000000..3f15b57c
--- /dev/null
+++ b/tests/protocols/gmpnext/entities/test_oci_image_targets.py
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: 2025 Greenbone AG
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from ...gmpnext import GMPTestCase
+from .oci_image_targets import (
+ GmpCloneOCIImageTargetTestMixin,
+ GmpCreateOCIImageTargetTestMixin,
+ GmpDeleteOCIImageTargetTestMixin,
+ GmpGetOCIImageTargetsTestMixin,
+ GmpGetOCIImageTargetTestMixin,
+ GmpModifyOCIImageTargetTestMixin,
+)
+
+
+class GmpCloneOCIImageTargetTestCase(
+ GmpCloneOCIImageTargetTestMixin, GMPTestCase
+):
+ pass
+
+
+class GmpCreateOCIImageTargetTestCase(
+ GmpCreateOCIImageTargetTestMixin, GMPTestCase
+):
+ pass
+
+
+class GmpDeleteOCIImageTargetTestCase(
+ GmpDeleteOCIImageTargetTestMixin, GMPTestCase
+):
+ pass
+
+
+class GmpGetOCIImageTargetTestCase(GmpGetOCIImageTargetsTestMixin, GMPTestCase):
+ pass
+
+
+class GmpGetOCIImageTargetsTestCase(GmpGetOCIImageTargetTestMixin, GMPTestCase):
+ pass
+
+
+class GmpModifyOCIImageTargetTestCase(
+ GmpModifyOCIImageTargetTestMixin, GMPTestCase
+):
+ pass