2020-02-06 19:00:41 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
import argparse
|
2020-02-19 23:11:53 +01:00
|
|
|
import os
|
2020-02-18 10:48:57 +01:00
|
|
|
import sys
|
2020-02-06 19:00:41 +01:00
|
|
|
|
|
|
|
from spoc import repo_local
|
2020-02-12 16:03:32 +01:00
|
|
|
from spoc import repo_online
|
2020-02-06 19:00:41 +01:00
|
|
|
from spoc import repo_publish
|
2020-03-12 20:56:52 +01:00
|
|
|
from spoc.cli import ActionQueue, print_lock, readable_size
|
|
|
|
from spoc.config import LOCK_FILE
|
2020-02-18 10:48:57 +01:00
|
|
|
from spoc.depsolver import DepSolver
|
2020-02-17 23:50:20 +01:00
|
|
|
from spoc.exceptions import ImageNotFoundError
|
2020-03-12 20:56:52 +01:00
|
|
|
from spoc.flock import locked
|
2020-02-06 19:00:41 +01:00
|
|
|
from spoc.image import Image
|
2020-02-07 15:12:27 +01:00
|
|
|
from spoc.imagebuilder import ImageBuilder
|
2020-02-06 19:00:41 +01:00
|
|
|
|
2020-02-14 10:54:22 +01:00
|
|
|
def get_image_name(file_path):
|
2020-02-06 19:00:41 +01:00
|
|
|
# Read and return image name from image file
|
2020-02-14 10:54:22 +01:00
|
|
|
with open(file_path) as f:
|
2020-02-06 19:00:41 +01:00
|
|
|
for line in f:
|
|
|
|
if line.startswith('IMAGE '):
|
|
|
|
return line.split()[1]
|
|
|
|
return None
|
|
|
|
|
2020-02-19 23:11:53 +01:00
|
|
|
def listing(list_type):
|
|
|
|
if list_type == 'installed':
|
2020-02-06 19:00:41 +01:00
|
|
|
images = repo_local.get_images()
|
2020-02-19 23:11:53 +01:00
|
|
|
elif list_type == 'online':
|
2020-02-06 19:00:41 +01:00
|
|
|
images = repo_online.get_images()
|
2020-02-19 23:11:53 +01:00
|
|
|
elif list_type == 'published':
|
2020-02-06 19:00:41 +01:00
|
|
|
images = repo_publish.get_images()
|
|
|
|
for image in images:
|
|
|
|
print(image)
|
|
|
|
|
2020-03-12 20:56:52 +01:00
|
|
|
@locked(LOCK_FILE, print_lock)
|
2020-02-06 19:00:41 +01:00
|
|
|
def download(image_name):
|
2020-02-17 23:50:20 +01:00
|
|
|
queue = ActionQueue()
|
2020-02-14 23:17:03 +01:00
|
|
|
local_images = repo_local.get_images()
|
|
|
|
for layer in repo_online.get_image(image_name)['layers']:
|
|
|
|
if layer not in local_images:
|
2020-02-17 23:50:20 +01:00
|
|
|
queue.download_image(Image(layer, False))
|
|
|
|
queue.process()
|
2020-02-06 19:00:41 +01:00
|
|
|
|
2020-03-12 20:56:52 +01:00
|
|
|
@locked(LOCK_FILE, print_lock)
|
2020-02-06 19:00:41 +01:00
|
|
|
def delete(image_name):
|
2020-02-18 10:48:57 +01:00
|
|
|
# Remove the image including all images that have it as one of its parents
|
|
|
|
# Check if image is in use
|
|
|
|
used = [c for c,d in repo_local.get_containers().items() if image_name in d['layers']]
|
|
|
|
if used:
|
|
|
|
sys.exit(f'Error: Image {image_name} is used by container{"s" if len(used) > 1 else ""} {", ".join(used)}')
|
|
|
|
# Build dependency tree to safely remove the images in order of dependency
|
|
|
|
depsolver = DepSolver()
|
|
|
|
for image,definition in repo_local.get_images().items():
|
|
|
|
if image_name in definition['layers']:
|
|
|
|
image = Image(image)
|
|
|
|
depsolver.add(image.name, image.layers, image)
|
|
|
|
# Enqueue and run the removal actions
|
2020-02-17 23:50:20 +01:00
|
|
|
queue = ActionQueue()
|
2020-02-18 10:48:57 +01:00
|
|
|
for image in reversed(depsolver.solve()):
|
|
|
|
queue.delete_image(image)
|
|
|
|
queue.process()
|
|
|
|
|
2020-03-12 20:56:52 +01:00
|
|
|
@locked(LOCK_FILE, print_lock)
|
2020-02-18 10:48:57 +01:00
|
|
|
def clean():
|
|
|
|
# Remove images which aren't used in any locally defined containers
|
|
|
|
used = set()
|
|
|
|
for definition in repo_local.get_containers().values():
|
|
|
|
used.update(definition['layers'])
|
|
|
|
# Build dependency tree to safely remove the images in order of dependency
|
|
|
|
depsolver = DepSolver()
|
|
|
|
for image in set(repo_local.get_images()) - used:
|
|
|
|
image = Image(image)
|
|
|
|
depsolver.add(image.name, image.layers, image)
|
|
|
|
# Enqueue and run the removal actions
|
|
|
|
queue = ActionQueue()
|
|
|
|
for image in reversed(depsolver.solve()):
|
|
|
|
queue.delete_image(image)
|
2020-02-17 23:50:20 +01:00
|
|
|
queue.process()
|
2020-02-06 19:00:41 +01:00
|
|
|
|
2020-03-12 20:56:52 +01:00
|
|
|
@locked(LOCK_FILE, print_lock)
|
2020-02-06 19:00:41 +01:00
|
|
|
def build(filename, force, do_publish):
|
|
|
|
# Check if a build is needed and attempt to build the image from image file
|
|
|
|
image_name = get_image_name(filename)
|
2020-02-07 15:12:27 +01:00
|
|
|
if force or image_name not in repo_local.get_images():
|
|
|
|
image = Image(image_name, False)
|
2020-02-19 23:11:53 +01:00
|
|
|
print(f'Building image {image_name} from file {os.path.abspath(filename)}')
|
2020-02-17 01:05:00 +01:00
|
|
|
image.delete()
|
|
|
|
image.create(ImageBuilder(), filename)
|
2020-02-06 19:00:41 +01:00
|
|
|
print(f'Image {image_name} built successfully')
|
|
|
|
# If publishing was requested, force publish after successful build
|
|
|
|
force = True
|
|
|
|
else:
|
|
|
|
print(f'Image {image_name} already built, skipping build task')
|
|
|
|
if do_publish:
|
|
|
|
publish(image_name, force)
|
|
|
|
|
|
|
|
def publish(image_name, force):
|
|
|
|
# Check if publishing is needed and attempt to publish the image
|
2020-02-07 15:12:27 +01:00
|
|
|
if force or image_name not in repo_publish.get_images():
|
2020-02-14 19:58:19 +01:00
|
|
|
image = Image(image_name)
|
2020-02-06 19:00:41 +01:00
|
|
|
print(f'Publishing image {image_name}')
|
2020-02-17 23:50:20 +01:00
|
|
|
image.unpublish()
|
2020-02-19 23:11:53 +01:00
|
|
|
size, dlsize = image.publish()
|
|
|
|
print(f'Image {image_name} compressed from {readable_size(size)} to {readable_size(dlsize)} and published successfully')
|
2020-02-06 19:00:41 +01:00
|
|
|
else:
|
|
|
|
print(f'Image {image_name} already published, skipping publish task')
|
|
|
|
|
|
|
|
def unpublish(image_name):
|
2020-02-12 16:03:32 +01:00
|
|
|
Image(image_name, False).unpublish()
|
2020-02-06 19:00:41 +01:00
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='SPOC image manager')
|
|
|
|
parser.set_defaults(action=None)
|
|
|
|
subparsers = parser.add_subparsers()
|
|
|
|
|
|
|
|
parser_list = subparsers.add_parser('list')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_list.set_defaults(action=listing)
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_list.add_argument('type', choices=('installed', 'online', 'published'), default='installed', const='installed', nargs='?')
|
|
|
|
|
|
|
|
parser_download = subparsers.add_parser('download')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_download.set_defaults(action=download)
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_download.add_argument('image')
|
|
|
|
|
|
|
|
parser_delete = subparsers.add_parser('delete')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_delete.set_defaults(action=delete)
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_delete.add_argument('image')
|
|
|
|
|
2020-02-18 10:48:57 +01:00
|
|
|
parser_clean = subparsers.add_parser('clean')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_clean.set_defaults(action=clean)
|
2020-02-18 10:48:57 +01:00
|
|
|
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_build = subparsers.add_parser('build')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_build.set_defaults(action=build)
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_build.add_argument('-f', '--force', action='store_true', help='Force rebuild already existing image')
|
|
|
|
parser_build.add_argument('-p', '--publish', action='store_true', help='Publish the image after successful build')
|
2020-02-19 23:11:53 +01:00
|
|
|
parser_build.add_argument('filename')
|
2020-02-06 19:00:41 +01:00
|
|
|
|
|
|
|
parser_publish = subparsers.add_parser('publish')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_publish.set_defaults(action=publish)
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_publish.add_argument('-f', '--force', action='store_true', help='Force republish already published image')
|
|
|
|
parser_publish.add_argument('image')
|
|
|
|
|
|
|
|
parser_unpublish = subparsers.add_parser('unpublish')
|
2020-02-18 12:33:54 +01:00
|
|
|
parser_unpublish.set_defaults(action=unpublish)
|
2020-02-06 19:00:41 +01:00
|
|
|
parser_unpublish.add_argument('image')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2020-02-18 12:33:54 +01:00
|
|
|
if args.action is listing:
|
2020-02-06 19:00:41 +01:00
|
|
|
listing(args.type)
|
2020-02-18 12:33:54 +01:00
|
|
|
elif args.action is download:
|
2020-02-06 19:00:41 +01:00
|
|
|
download(args.image)
|
2020-02-18 12:33:54 +01:00
|
|
|
elif args.action is delete:
|
2020-02-06 19:00:41 +01:00
|
|
|
delete(args.image)
|
2020-02-18 12:33:54 +01:00
|
|
|
elif args.action is clean:
|
2020-02-18 10:48:57 +01:00
|
|
|
clean()
|
2020-02-18 12:33:54 +01:00
|
|
|
elif args.action is build:
|
2020-02-19 23:11:53 +01:00
|
|
|
build(args.filename, args.force, args.publish)
|
2020-02-18 12:33:54 +01:00
|
|
|
elif args.action is publish:
|
2020-02-06 19:00:41 +01:00
|
|
|
publish(args.image, args.force)
|
2020-02-18 12:33:54 +01:00
|
|
|
elif args.action is unpublish:
|
2020-02-17 01:05:00 +01:00
|
|
|
unpublish(args.image)
|
2020-02-06 19:00:41 +01:00
|
|
|
else:
|
|
|
|
parser.print_usage()
|