Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add parser container_inspect #3562

Merged
merged 22 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/custom_datasources_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ insights.specs.datasources.cloud_init
:show-inheritance:
:undoc-members:

insights.specs.datasources.container.containers_inspect
-------------------------------------------------------

.. automodule:: insights.specs.datasources.container.containers_inspect
:members: running_rhel_containers_id, containers_inspect_data_datasource
:show-inheritance:
:undoc-members:

insights.specs.datasources.container
------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions docs/shared_parsers_catalog/containers_inspect.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.. automodule:: insights.parsers.containers_inspect
:members:
:show-inheritance:
47 changes: 47 additions & 0 deletions insights/parsers/containers_inspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
ContainersInspect - commands ``docker|podman inspect``
======================================================

This parser reads the output of commands: "/usr/bin/docker|podman inspect <containers ID>"
which are used to show the metadata information of containers.
"""
from insights import parser, JSONParser
from insights.specs import Specs


@parser(Specs.containers_inspect)
class ContainersInspect(JSONParser):
"""
Class for parsing the output of the containers inspect commands
``/usr/bin/docker|podman inspect <containers ID>``


Typical Output of this command after datasource containers_inspect is::

[
{
"Id": "aeaea3ead527",
"Image": "538460c14d75dee1504e56ad8ddb7fe039093b1530ef8f90442a454b9aa3dc8b",
"engine": "podman",
"HostConfig|Privileged": false,
"Config|Cmd": ["sleep", "1000000"]
}
]

Attributes:
data (list): A list containing the parsed information

Examples:
>>> from insights.core.filters import add_filter
>>> from insights.specs import Specs
>>> add_filter(Specs.container_inspect_keys, ['HostConfig|Privileged'])
>>> str(inspect_containers.data[0]["Id"])
xiangce marked this conversation as resolved.
Show resolved Hide resolved
'aeaea3ead527'
>>> str(inspect_containers.data[0]["engine"])
'podman'
>>> inspect_containers.data[0]["HostConfig|Privileged"]
False
"""

def parse_content(self, content):
super(ContainersInspect, self).parse_content(content[0])
24 changes: 23 additions & 1 deletion insights/parsers/docker_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,29 @@
from insights.core.marshalling import unmarshal
from insights.parsers import SkipException
from insights.specs import Specs
from insights.util import deprecated


class DockerInspect(CommandParser, dict):
"""
.. warning::
This class is deprecated, please use
:py:class:`insights.parsers.containers_inspect.ContainersInspect` instead.

Parse the output of command "docker inspect --type=image" and "docker
inspect --type=container". The output of these two commands is formatted
as JSON, so "json.loads" is an option to parse the output in the future.

Raises:
SkipException: If content is not provided
"""
def __init__(self, *args, **kwargs):
deprecated(
DockerInspect,
"Please use the :class:`insights.parsers.containers_inspect.ContainersInspect` instead.",
"3.2.25"
)
super(DockerInspect, self).__init__(*args, **kwargs)

def parse_content(self, content):
if not content:
Expand Down Expand Up @@ -70,6 +82,10 @@ class DockerInspectImage(DockerInspect):
@parser(Specs.docker_container_inspect)
class DockerInspectContainer(DockerInspect):
"""
.. warning::
This parser is deprecated, please use
:py:class:`insights.parsers.containers_inspect.ContainersInspect` instead.

Parse docker container inspect output using the DockerInspect parser class.

Sample input::
Expand Down Expand Up @@ -98,4 +114,10 @@ class DockerInspectContainer(DockerInspect):
False

"""
pass
def __init__(self, *args, **kwargs):
deprecated(
DockerInspectContainer,
"Please use the :class:`insights.parsers.containers_inspect.ContainersInspect` instead.",
"3.2.25"
)
super(DockerInspectContainer, self).__init__(*args, **kwargs)
24 changes: 23 additions & 1 deletion insights/parsers/podman_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,29 @@
from insights.core.marshalling import unmarshal
from insights.parsers import SkipException
from insights.specs import Specs
from insights.util import deprecated


class PodmanInspect(CommandParser, dict):
"""
.. warning::
This class is deprecated, please use
:py:class:`insights.parsers.containers_inspect.ContainersInspect` instead.

Parse the output of command "podman inspect --type=image" and "podman
inspect --type=container". The output of these two commands is formatted
as JSON, so "json.loads" is an option to parse the output in the future.

Raises:
SkipException: If content is not provided
"""
def __init__(self, *args, **kwargs):
deprecated(
PodmanInspect,
"Please use the :class:`insights.parsers.containers_inspect.ContainersInspect` instead.",
"3.2.25"
)
super(PodmanInspect, self).__init__(*args, **kwargs)

def parse_content(self, content):
if not content:
Expand Down Expand Up @@ -66,6 +78,10 @@ class PodmanInspectImage(PodmanInspect):
@parser(Specs.podman_container_inspect)
class PodmanInspectContainer(PodmanInspect):
"""
.. warning::
This parser is deprecated, please use
:py:class:`insights.parsers.containers_inspect.ContainersInspect` instead.

Parse podman container inspect output using the PodmanInspect parser class.

Sample input::
Expand All @@ -91,4 +107,10 @@ class PodmanInspectContainer(PodmanInspect):
>>> container.get('State').get('Paused') is False
True
"""
pass
def __init__(self, *args, **kwargs):
deprecated(
PodmanInspectContainer,
"Please use the :class:`insights.parsers.containers_inspect.ContainersInspect` instead.",
"3.2.25"
)
super(PodmanInspectContainer, self).__init__(*args, **kwargs)
2 changes: 2 additions & 0 deletions insights/specs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,3 +792,5 @@ class Specs(SpecSet):
container_redhat_release = RegistryPoint(multi_output=True)
container_nginx_conf = RegistryPoint(multi_output=True)
container_installed_rpms = RegistryPoint(multi_output=True)
container_inspect_keys = RegistryPoint(filterable=True)
containers_inspect = RegistryPoint()
94 changes: 94 additions & 0 deletions insights/specs/datasources/container/containers_inspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
Custom datasources for containers inspect information
"""
import json
import os
from insights.core.context import HostContext
from insights.core.dr import SkipComponent
from insights.core.filters import get_filters
from insights.core.plugins import datasource, ContentException
from insights.core.spec_factory import DatasourceProvider, foreach_execute
from insights.specs import Specs
from insights.specs.datasources.container import running_rhel_containers


@datasource(running_rhel_containers, HostContext)
def running_rhel_containers_id(broker):
"""
Returns a list of container_id of the running containers.
"""
containers_info = []
for container in broker[running_rhel_containers]:
containers_info.append((container[1], container[2]))
return containers_info


class LocalSpecs(Specs):
""" Local specs used only by docker|podman_inspect datasources """

containers_inspect_data_raw = foreach_execute(running_rhel_containers_id, "/usr/bin/%s inspect %s")
""" Returns the output of command ``/usr/bin/docker|podman inspect <container ID>`` """


@datasource(LocalSpecs.containers_inspect_data_raw, HostContext)
def containers_inspect_data_datasource(broker):
"""
This datasource provides the filtered information collected
from ``/usr/bin/docker|podman inspect <container ID>``.

.. note::
The path of the target key is like raw_data[key1][key2][target_key],
then the filter pattern is like "key1|key2|target_key".
If the value type of raw_data[key1][key2] is list, although the target_key is in the list,
the filter pattern must be "key1|key2", this datasource returns the whole value of the list.
The value of "Id" and "Image" are checked from raw data directly, no need filter these items.

Typical content of ``/usr/bin/docker|podman inspect <container ID>`` file is::

[
{
"Id": "aeaea3ead52724bb525bb2b5c619d67836250756920f0cb9884431ba53b476d8",
"Created": "2022-10-21T23:47:24.506159696-04:00",
"Path": "sleep"
...
}
...
]

Returns:
list: item is JSON string containing filtered information.

Raises:
SkipComponent: When the filter/path does not exist or any exception occurs.
"""
try:
no_filters = ['Id', 'Image']
filters = sorted(set(get_filters(Specs.container_inspect_keys)) - set(no_filters))
if filters:
total_results = []
for item in broker[LocalSpecs.containers_inspect_data_raw]:
raw_data = json.loads(''.join(item.content))[0]
engine, _, container_id = item.cmd.split(None)
filter_result = dict(Id=container_id, engine=os.path.basename(engine))
if 'Image' in raw_data:
filter_result['Image'] = raw_data['Image'].split("sha256:")[-1]
for item in filters:
val = raw_data
for key in item.split("|"):
if key in val:
val = val[key]
else:
break
# If the filtered key does not exist, skip it
if val == raw_data:
continue
filter_result[item] = val
total_results.append(filter_result)
# Generally the False branch of this condition will never reach, since the required component
# LocalSpecs.containers_inspect_data_raw can guarantee total_results is not null. However, it's worth
# leaving this condition as an explicit assertion to avoid unexpected situation.
if total_results:
xiangce marked this conversation as resolved.
Show resolved Hide resolved
return DatasourceProvider(content=json.dumps(total_results), relative_path='insights_containers/containers_inspect')
except Exception as e:
raise ContentException("Unexpected content exception:{e}".format(e=str(e)))
raise SkipComponent
3 changes: 2 additions & 1 deletion insights/specs/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
semanage, ssl_certificate, sys_fs_cgroup_memory_tasks_number, system_user_dirs, user_group, yum_updates, luks_devices)
from insights.specs.datasources.sap import sap_hana_sid, sap_hana_sid_SID_nr
from insights.specs.datasources.pcp import pcp_enabled, pmlog_summary_args
from insights.specs.datasources.container import running_rhel_containers
from insights.specs.datasources.container import running_rhel_containers, containers_inspect
from insights.specs.datasources.container.nginx_conf import nginx_conf as container_nginx_conf_ds


Expand Down Expand Up @@ -679,3 +679,4 @@ class DefaultSpecs(Specs):
container_installed_rpms = container_execute(running_rhel_containers, "rpm -qa --qf '%s'" % format_rpm(), context=HostContext, signum=signal.SIGTERM)
container_nginx_conf = container_collect(container_nginx_conf_ds)
container_redhat_release = container_collect(running_rhel_containers, "/etc/redhat-release")
containers_inspect = containers_inspect.containers_inspect_data_datasource
Loading