Update so-docker-prune

This commit is contained in:
Mike Reeves
2025-05-21 13:47:45 -04:00
committed by GitHub
parent 2911025c0c
commit ddd023c69a

View File

@@ -4,22 +4,16 @@
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at # or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the # https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0. # Elastic License 2.0.
import sys, argparse, re, subprocess, json
import sys, argparse, re, docker
from packaging.version import Version, InvalidVersion from packaging.version import Version, InvalidVersion
from itertools import groupby, chain from itertools import groupby, chain
def get_image_name(string) -> str: def get_image_name(string) -> str:
return ':'.join(string.split(':')[:-1]) return ':'.join(string.split(':')[:-1])
def get_so_image_basename(string) -> str: def get_so_image_basename(string) -> str:
return get_image_name(string).split('/so-')[-1] return get_image_name(string).split('/so-')[-1]
def get_image_version(string) -> str: def get_image_version(string) -> str:
ver = string.split(':')[-1] ver = string.split(':')[-1]
if ver == 'latest': if ver == 'latest':
@@ -35,33 +29,49 @@ def get_image_version(string) -> str:
return '999999.9.9' return '999999.9.9'
return ver return ver
def run_command(command):
process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if process.returncode != 0:
print(f"Error executing command: {command}", file=sys.stderr)
print(f"Error message: {process.stderr}", file=sys.stderr)
exit(1)
return process.stdout
def main(quiet): def main(quiet):
client = docker.from_env() try:
# Prune old/stopped containers using docker CLI
# Prune old/stopped containers
if not quiet: print('Pruning old containers') if not quiet: print('Pruning old containers')
client.containers.prune() run_command('docker container prune -f')
image_list = client.images.list(filters={ 'dangling': False }) # Get list of images using docker CLI
images_json = run_command('docker images --format "{{json .}}"')
# Map list of image objects to flattened list of tags (format: "name:version") # Parse the JSON output
tag_list = list(chain.from_iterable(list(map(lambda x: x.attrs.get('RepoTags'), image_list)))) image_list = []
for line in images_json.strip().split('\n'):
if line: # Skip empty lines
image_list.append(json.loads(line))
# Extract tags in the format "name:version"
tag_list = []
for img in image_list:
# Skip dangling images
if img.get('Repository') != "<none>" and img.get('Tag') != "<none>":
tag = f"{img.get('Repository')}:{img.get('Tag')}"
# Filter to only SO images (base name begins with "so-") # Filter to only SO images (base name begins with "so-")
tag_list = list(filter(lambda x: re.match(r'^.*\/so-[^\/]*$', get_image_name(x)), tag_list)) if re.match(r'^.*\/so-[^\/]*$', get_image_name(tag)):
tag_list.append(tag)
# Group tags into lists by base name (sort by same projection first) # Group tags into lists by base name (sort by same projection first)
tag_list.sort(key=lambda x: get_so_image_basename(x)) tag_list.sort(key=lambda x: get_so_image_basename(x))
grouped_tag_lists = [ list(it) for _, it in groupby(tag_list, lambda x: get_so_image_basename(x)) ] grouped_tag_lists = [list(it) for k, it in groupby(tag_list, lambda x: get_so_image_basename(x))]
no_prunable = True no_prunable = True
for t_list in grouped_tag_lists: for t_list in grouped_tag_lists:
try: try:
# Group tags by version, in case multiple images exist with the same version string # Group tags by version, in case multiple images exist with the same version string
t_list.sort(key=lambda x: Version(get_image_version(x)), reverse=True) t_list.sort(key=lambda x: Version(get_image_version(x)), reverse=True)
grouped_t_list = [ list(it) for _,it in groupby(t_list, lambda x: get_image_version(x)) ] grouped_t_list = [list(it) for k, it in groupby(t_list, lambda x: get_image_version(x))]
# Keep the 2 most current version groups # Keep the 2 most current version groups
if len(grouped_t_list) <= 2: if len(grouped_t_list) <= 2:
continue continue
@@ -71,10 +81,10 @@ def main(quiet):
for tag in group: for tag in group:
if not quiet: print(f'Removing image {tag}') if not quiet: print(f'Removing image {tag}')
try: try:
client.images.remove(tag, force=True) run_command(f'docker rmi -f {tag}')
except docker.errors.ClientError as e: except Exception as e:
print(f'Could not remove image {tag}, continuing...') print(f'Could not remove image {tag}, continuing...')
except (docker.errors.APIError, InvalidVersion) as e: except (InvalidVersion) as e:
print(f'so-{get_so_image_basename(t_list[0])}: {e}', file=sys.stderr) print(f'so-{get_so_image_basename(t_list[0])}: {e}', file=sys.stderr)
exit(1) exit(1)
except Exception as e: except Exception as e:
@@ -85,6 +95,9 @@ def main(quiet):
if no_prunable and not quiet: if no_prunable and not quiet:
print('No Security Onion images to prune') print('No Security Onion images to prune')
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
exit(1)
if __name__ == "__main__": if __name__ == "__main__":
main_parser = argparse.ArgumentParser(add_help=False) main_parser = argparse.ArgumentParser(add_help=False)