Skip to content

Commit

Permalink
Move logic out of storage.py (#23)
Browse files Browse the repository at this point in the history
* Move utils methods to utils.py

* ShowContainers is now GetContainersString

* Add exception message

* remove useless unicode workaround
  • Loading branch information
rgayon authored Jun 8, 2018
1 parent 897201a commit 2b2f32c
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 322 deletions.
122 changes: 94 additions & 28 deletions docker_explorer/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,13 @@
from __future__ import print_function, unicode_literals

import argparse
import codecs
import os
import sys

from docker_explorer import errors
from docker_explorer.lib import container
from docker_explorer.lib import aufs
from docker_explorer.lib import overlay

# This is to fix UnicodeEncodeError issues when python
# suddenly changes the output encoding when sys.stdout is
# piped into something else.
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
from docker_explorer.lib import utils


class DockerExplorer(object):
Expand All @@ -47,29 +42,36 @@ class DockerExplorer(object):
def __init__(self):
"""Initializes the ContainerInfo class."""
self._argument_parser = None
self.docker_directory = '/var/lib/docker'
self.container_config_filename = 'config.v2.json'
self.containers_directory = None
self.docker_directory = None
self.docker_version = 2
self.storage_object = None

def DetectStorage(self):
"""Detects the storage backend.
def _SetDockerDirectory(self, docker_path):
"""Sets the Docker main directory.
More info :
https://docs.docker.com/engine/userguide/storagedriver/
http://jpetazzo.github.io/assets/2015-06-04-deep-dive-into-docker-storage-drivers.html#60
Args:
docker_path(str): the absolute path to the docker directory.
Raises:
errors.BadStorageException: If the storage backend couldn't be detected.
"""
self.docker_directory = docker_path
if not os.path.isdir(self.docker_directory):
err_message = (
'{0:s} is not a Docker directory\n'
'Please specify the Docker\'s directory path.\n'
'hint: de.py -r /var/lib/docker').format(self.docker_directory)
raise errors.BadStorageException(err_message)

self.containers_directory = os.path.join(
self.docker_directory, 'containers')

if os.path.isfile(
os.path.join(self.docker_directory, 'repositories-aufs')):
# Handles Docker engine storage versions 1.9 and below.
# TODO: check this agains other storages in version 1.9 and below
self.docker_version = 1
self.storage_object = aufs.AufsStorage(
docker_directory=self.docker_directory, docker_version=1)
elif os.path.isdir(os.path.join(self.docker_directory, 'overlay2')):
Expand Down Expand Up @@ -105,7 +107,7 @@ def AddBasicOptions(self, argument_parser):
def AddMountCommand(self, args):
"""Adds the mount command to the argument_parser.
args:
Args:
args (argument_parser): the argument parser to add the command to.
"""
mount_parser = args.add_parser(
Expand Down Expand Up @@ -164,13 +166,50 @@ def ParseArguments(self):
return opts

def ParseOptions(self, options):
"""Parses the command line options.
"""Parses the command line options."""
self.docker_directory = os.path.abspath(options.docker_directory)

def GetContainer(self, container_id):
"""Returns a Container object given a container_id.
Args:
container_id (str): the ID of the container.
Returns:
Namespace: the populated namespace.
container.Container: the container object.
"""
return container.Container(
self.docker_directory, container_id, docker_version=self.docker_version)

self.docker_directory = os.path.abspath(options.docker_directory)
def GetAllContainers(self):
"""Gets a list containing information about all containers.
Returns:
list(Container): the list of Container objects.
"""
container_ids_list = os.listdir(self.containers_directory)
if not container_ids_list:
print('Couldn\'t find any container configuration file (\'{0:s}\'). '
'Make sure the docker repository ({1:s}) is correct. '
'If it is correct, you might want to run this script'
' with higher privileges.').format(
self.container_config_filename, self.docker_directory)
return [self.GetContainer(cid) for cid in container_ids_list]

def GetContainersList(self, only_running=False):
"""Returns a list of Container objects, sorted by start time.
Args:
only_running (bool): Whether we return only running Containers.
Returns:
list(Container): list of Containers information objects.
"""
containers_list = sorted(
self.GetAllContainers(), key=lambda x: x.start_timestamp)
if only_running:
containers_list = [x for x in containers_list if x.running]
return containers_list

def Mount(self, container_id, mountpoint):
"""Mounts the specified container's filesystem.
Expand All @@ -179,24 +218,51 @@ def Mount(self, container_id, mountpoint):
container_id (str): the ID of the container.
mountpoint (str): the path to the destination mount point.
"""
if self.storage_object is None:
self.DetectStorage()
self.storage_object.Mount(container_id, mountpoint)
container_object = self.GetContainer(container_id)
self.storage_object.Mount(container_object, mountpoint)

def GetContainersString(self, only_running=False):
"""Returns a string describing the running containers.
Args:
only_running (bool): Whether we display only running Containers.
Returns:
str: the string displaying information about running containers.
"""
result_string = ''
for container_object in self.GetContainersList(only_running=only_running):
image_id = container_object.image_id
if self.docker_version == 2:
image_id = image_id.split(':')[1]

if container_object.config_labels:
labels_list = ['{0:s}: {1:s}'.format(k, v) for (k, v) in
container_object.config_labels.items()]
labels_str = ', '.join(labels_list)
result_string += 'Container id: {0:s} / Labels : {1:s}\n'.format(
container_object.container_id, labels_str)
else:
result_string += 'Container id: {0:s} / No Label\n'.format(
container_object.container_id)
result_string += '\tStart date: {0:s}\n'.format(
utils.FormatDatetime(container_object.start_timestamp))
result_string += '\tImage ID: {0:s}\n'.format(image_id)
result_string += '\tImage Name: {0:s}\n'.format(
container_object.config_image_name)

return result_string

def ShowContainers(self, only_running=False):
"""Displays the running containers.
Args:
only_running (bool): Whether we display only running Containers.
"""
if self.storage_object is None:
self.DetectStorage()
print(self.storage_object.ShowContainers(only_running=only_running))
print(self.GetContainersString(only_running=only_running))

def ShowRepositories(self):
"""Displays information about the images in the Docker repository."""
if self.storage_object is None:
self.DetectStorage()
print(self.storage_object.ShowRepositories())

def ShowHistory(self, container_id, show_empty_layers=False):
Expand All @@ -206,8 +272,6 @@ def ShowHistory(self, container_id, show_empty_layers=False):
container_id (str): the ID of the container.
show_empty_layers (bool): whether to display empty layers.
"""
if self.storage_object is None:
self.DetectStorage()
print(self.storage_object.GetHistory(container_id, show_empty_layers))

def Main(self):
Expand All @@ -221,6 +285,8 @@ def Main(self):
options = self.ParseArguments()
self.ParseOptions(options)

self._SetDockerDirectory(self.docker_directory)


if options.command == 'mount':
self.Mount(options.container_id, options.mountpoint)
Expand Down
5 changes: 2 additions & 3 deletions docker_explorer/lib/aufs.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ def GetImageInfo(self, image_id):
return '{0:s}:{1:s}'.format(name, version)
return 'not found'

def MakeMountCommands(self, container_id, mount_dir):
def MakeMountCommands(self, container_object, mount_dir):
"""Generates the required shell commands to mount a container's ID.
Args:
container_id (str): the container ID to mount.
container_object (Container): the container object to mount.
mount_dir (str): the path to the target mount point.
Returns:
Expand All @@ -113,7 +113,6 @@ def MakeMountCommands(self, container_id, mount_dir):
print('Could not find /sbin/mount.aufs. Please install the aufs-tools '
'package.')

container_object = self.GetContainer(container_id)
mount_id = container_object.mount_id

container_layers_filepath = os.path.join(
Expand Down
43 changes: 37 additions & 6 deletions docker_explorer/lib/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from __future__ import print_function, unicode_literals

import json
import os

from docker_explorer import errors


class Container(object):
Expand All @@ -38,32 +41,60 @@ class Container(object):
container. (Docker storage backend v1).
"""

def __init__(self, container_id, container_info_json_path):
def __init__(self, docker_directory, container_id, docker_version=2):
"""Initializes the Container class.
Args:
docker_directory (str): the absolute path to the Docker directory.
container_id (str): the container ID.
container_info_json_path (str): the path to the JSON file containing the
container's information.
docker_version (int): (Optional) the version of the Docker storage module.
Raises:
errors.BadContainerException: if there was an error when parsing
container_info_json_path
"""
self.container_id = container_id
self.container_config_filename = 'config.v2.json'
if docker_version == 1:
self.container_config_filename = 'config.json'

self.docker_directory = docker_directory

container_info_json_path = os.path.join(
self.docker_directory, 'containers', container_id,
self.container_config_filename)
with open(container_info_json_path) as container_info_json_file:
container_info_dict = json.load(container_info_json_file)

if container_info_dict is None:
raise errors.BadContainerException(
'Could not load container configuration file {0}'.format(
container_info_json_path)
)

self.container_id = container_info_dict.get('ID', None)
json_config = container_info_dict.get('Config', None)
if json_config:
self.config_image_name = json_config.get('Image', None)
self.config_labels = json_config.get('Labels', None)
self.creation_timestamp = container_info_dict.get('Created', None)
self.image_id = container_info_dict.get('Image', None)
self.mount_id = None
self.mount_points = container_info_dict.get('MountPoints', None)
self.name = container_info_dict.get('Name', '')
json_state = container_info_dict.get('State', None)
if json_state:
self.running = json_state.get('Running', False)
self.start_timestamp = json_state.get('StartedAt', False)
self.storage_driver = json_config.get('Driver', None)
self.storage_driver = container_info_dict.get('Driver', None)
if self.storage_driver is None:
raise errors.BadContainerException(
'{0} container config file lacks Driver key'.format(
container_info_json_path))
self.volumes = container_info_dict.get('Volumes', None)

self.mount_id = None
if docker_version == 2:
c_path = os.path.join(
self.docker_directory, 'image', self.storage_driver, 'layerdb',
'mounts', container_id)
with open(os.path.join(c_path, 'mount-id')) as mount_id_file:
self.mount_id = mount_id_file.read()
5 changes: 2 additions & 3 deletions docker_explorer/lib/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,17 @@ def _BuildLowerLayers(self, lower):
return os.path.join(
self.docker_directory, self.STORAGE_METHOD, lower.strip(), 'root')

def MakeMountCommands(self, container_id, mount_dir):
def MakeMountCommands(self, container_object, mount_dir):
"""Generates the required shell commands to mount a container's ID.
Args:
container_id (str): the container ID to mount.
container_object (Container): the container object.
mount_dir (str): the path to the target mount point.
Returns:
list: a list commands that needs to be run to mount the container's view
of the file system.
"""
container_object = self.GetContainer(container_id)
mount_id_path = os.path.join(
self.docker_directory, self.STORAGE_METHOD, container_object.mount_id)

Expand Down
Loading

0 comments on commit 2b2f32c

Please sign in to comment.