summaryrefslogtreecommitdiffstats
path: root/cli
diff options
context:
space:
mode:
authorAlejandro Sirgo Rica <asirgo@soleta.eu>2024-11-07 16:26:55 +0100
committerAlejandro Sirgo Rica <asirgo@soleta.eu>2024-11-11 12:16:04 +0100
commit38f373addb05b14abb8de6c29408a4fe215a5bbc (patch)
tree32a8d4204f24bc906944959bb668e92368248d3f /cli
parenta219bc7ff729516aea7ddea8516b50d96e7d9363 (diff)
cli: add live management commands
Add command to install the files of a live system. Example: ogcli install live --name ogrelive-6.1.0-26 Perform an update if live files are already present. Add command to delete the files of a live system. Example: ogcli delete live --name ogrelive-6.1.0-26 Update ogcli list live to show the lives in the server when invoked with the --remote flag. Example: ogcli live live --remote
Diffstat (limited to 'cli')
-rw-r--r--cli/cli.py22
-rw-r--r--cli/objects/live.py185
-rw-r--r--cli/utils.py20
3 files changed, 224 insertions, 3 deletions
diff --git a/cli/cli.py b/cli/cli.py
index 11d1521..0e34cd0 100644
--- a/cli/cli.py
+++ b/cli/cli.py
@@ -113,7 +113,7 @@ class OgCLI():
elif parsed_args.item == 'servers':
ret = OgServer.list_servers(self.rest)
elif parsed_args.item == 'live':
- ret = OgLive.list_live(self.rest)
+ ret = OgLive.list_live(self.rest, args[1:])
return ret
def set(self, args):
@@ -231,7 +231,7 @@ class OgCLI():
return ret
def delete(self, args):
- choices = ['server', 'repo', 'center', 'room', 'client', 'folder']
+ choices = ['server', 'repo', 'center', 'room', 'client', 'folder', 'live']
parser = argparse.ArgumentParser(prog='ogcli delete')
parser.add_argument('delete_obj', choices=choices)
@@ -253,6 +253,8 @@ class OgCLI():
ret = OgClient.delete_client(self.rest, args[1:])
elif parsed_args.delete_obj == 'folder':
ret = OgFolder.delete_folder(self.rest, args[1:])
+ elif parsed_args.delete_obj == 'live':
+ ret = OgLive.delete_live(self.rest, args[1:])
return ret
def add(self, args):
@@ -280,3 +282,19 @@ class OgCLI():
elif parsed_args.add_obj == 'folder':
ret = OgFolder.add_folder(self.rest, args[1:])
return ret
+
+ def install(self, args):
+ choices = ['live']
+ parser = argparse.ArgumentParser(prog='ogcli install')
+ parser.add_argument('install_obj', choices=choices)
+
+ if not args:
+ print('Missing install subcommand', file=sys.stderr)
+ parser.print_help(file=sys.stderr)
+ return 1
+
+ parsed_args = parser.parse_args([args[0]])
+ ret = 0
+ if parsed_args.install_obj == 'live':
+ ret = OgLive.install_live(self.rest, args[1:])
+ return ret
diff --git a/cli/objects/live.py b/cli/objects/live.py
index 7a057bf..7489d7c 100644
--- a/cli/objects/live.py
+++ b/cli/objects/live.py
@@ -7,14 +7,197 @@
import argparse
+from config import cfg, OG_CLI_CFG_PATH
from cli.utils import *
+import requests
+import shutil
+import sys
+import os
class OgLive():
+ live_files = [
+ 'oginitrd.img',
+ 'ogvmlinuz',
+ 'filesystem.squashfs',
+ 'oginitrd.img.full.sum',
+ 'ogvmlinuz.full.sum',
+ 'filesystem.squashfs.full.sum'
+ ]
+ tmp_extension = '.tmp'
+
+ @staticmethod
+ def _get_local_live_path():
+ local_live = cfg.get('local_live', '/var/www/html/ogrelive')
+ if not local_live:
+ print(f'Error: local_live not defined in {OG_CLI_CFG_PATH}')
+ return None
+
+ if not os.path.isdir(local_live):
+ print(f'Error: {local_live} directoy does not exist')
+ return None
+ return local_live
+
@staticmethod
- def list_live(rest):
+ def list_live(rest, args):
+ parser = argparse.ArgumentParser(prog='ogcli list live')
+ parser.add_argument('--remote',
+ action='store_true',
+ help='(Optional) Obtain the list of the remote instead of the local lives')
+ parsed_args = parser.parse_args(args)
+
+ if parsed_args.remote:
+ local_live = OgLive._get_local_live_path()
+
+ if not local_live:
+ return 1
+
+ remote_json = os.path.join(local_live, 'ogrelive.json')
+ try:
+ with open(remote_json, 'r') as json_file:
+ remote_data = json_file.read()
+ except json.JSONDecodeError:
+ print(f'ERROR: Failed parse malformed JSON file {remote_json}')
+ return 1
+ except OSError as e:
+ print(f'ERROR: cannot open {remote_json}: {e}')
+ return 1
+
+ print_json(remote_data)
+ return 0
+
res = rest.get('/oglive/list')
if not res:
return 1
print_json(res.text)
return 0
+
+ @staticmethod
+ def _delete_live_files(live_name, extension):
+ local_live = OgLive._get_local_live_path()
+
+ if not local_live:
+ return 1
+
+ ret = 0
+
+ folder_path = os.path.join(local_live, live_name)
+ for live_file in OgLive.live_files:
+ target_file = os.path.join(folder_path, live_file) + extension
+ try:
+ os.remove(target_file)
+ except OSError as e:
+ print(f'Error deleting {target_file}: {e}')
+ ret = 1
+ print(f'Successfully deleted {target_file}')
+ return ret
+
+ @staticmethod
+ def install_live(rest, args):
+ parser = argparse.ArgumentParser(prog='ogcli install live')
+ parser.add_argument('--name',
+ nargs='?',
+ required=True,
+ help='Name of the center')
+ parsed_args = parser.parse_args(args)
+
+ live_name = parsed_args.name
+
+ server_live = cfg.get('server_live', 'https://opengnsys.soleta.eu/ogrelive')
+ local_live = OgLive._get_local_live_path()
+
+ if not local_live:
+ return 1
+
+ local_dir = os.path.join(local_live, live_name)
+ if os.path.exists(local_dir):
+ print(f'Local files for {live_name} found, updating files...')
+ try:
+ os.makedirs(local_dir, exist_ok=True)
+ except OSError as e:
+ print(f'ERROR: Failed to create directory {local_dir}: {e}')
+ return 1
+
+ # File download
+ remote_dir = f'{server_live}/{live_name}'
+
+ for live_file in OgLive.live_files:
+ file_url = f'{remote_dir}/{live_file}'
+ local_file = os.path.join(local_dir, live_file)
+
+ response = requests.get(file_url, stream=True, timeout=(5, None))
+
+ if response.status_code == 200:
+ with open(local_file + OgLive.tmp_extension, 'wb') as f:
+ for chunk in response.iter_content(chunk_size=8192):
+ f.write(chunk)
+ print(f'Successfully downloaded {live_file}')
+ else:
+ print(f'ERROR: Failed to download {live_file}. Status code: {response.status_code}')
+ OgLive._delete_live_files(live_name, OgLive.tmp_extension)
+ return 1
+
+ # Checksum verification
+ check_files = [
+ os.path.join(local_dir, 'oginitrd.img'),
+ os.path.join(local_dir, 'ogvmlinuz'),
+ os.path.join(local_dir, 'filesystem.squashfs'),
+ ]
+ for check_file in check_files:
+ local_checksum = compute_md5(check_file + OgLive.tmp_extension)
+ try:
+ with open(check_file + '.full.sum' + OgLive.tmp_extension, 'r') as f:
+ remote_checksum = f.read().strip()
+ except (FileNotFoundError, PermissionError, OSError) as e:
+ print(f'ERROR: Cannot read checksum file {check_file}.full.sum: {e}')
+ OgLive._delete_live_files(live_name, OgLive.tmp_extension)
+ return -1
+
+ if local_checksum != remote_checksum:
+ print(f'ERROR: Checksum mismatch for {check_file}')
+ OgLive._delete_live_files(live_name, OgLive.tmp_extension)
+ return -1
+
+ print(f'Checksum is OK for {check_file}')
+
+ # move tmp files
+ for live_file in OgLive.live_files:
+ target_file = os.path.join(local_dir, live_file)
+ src_file = target_file + OgLive.tmp_extension
+
+ shutil.move(src_file, target_file)
+
+ payload = {'name': live_name}
+ res = rest.post('/oglive/add', payload=payload)
+
+ if not res:
+ return 1
+ return 0
+
+ @staticmethod
+ def delete_live(rest, args):
+ parser = argparse.ArgumentParser(prog='ogcli delete live')
+ parser.add_argument('--name',
+ nargs='?',
+ required=True,
+ help='Name of the center')
+ parsed_args = parser.parse_args(args)
+ live_name = parsed_args.name
+
+ payload = {'name': live_name}
+ res = rest.post('/oglive/delete', payload=payload)
+
+ if not res:
+ return 1
+
+ local_live = OgLive._get_local_live_path()
+
+ if not local_live:
+ return 1
+
+ local_dir = os.path.join(local_live, live_name)
+
+ if os.path.exists(local_dir):
+ shutil.rmtree(local_dir)
+
+ return 0
diff --git a/cli/utils.py b/cli/utils.py
index 3825583..380bd5b 100644
--- a/cli/utils.py
+++ b/cli/utils.py
@@ -8,7 +8,9 @@
import unicodedata
import json
import ipaddress
+import hashlib
import re
+import os
def scope_lookup(scope_id, scope_type, d):
if scope_id == d.get('id') and scope_type == d.get('type'):
@@ -69,3 +71,21 @@ def check_mac_address(addr):
def remove_accents(text):
normalized_text = unicodedata.normalize('NFD', text)
return ''.join(c for c in normalized_text if unicodedata.category(c) != 'Mn')
+
+def compute_md5(path, bs=2**20):
+ if not os.path.exists(path):
+ print(f"Failed to calculate checksum, image file {path} does not exist")
+ return None
+
+ m = hashlib.md5()
+ try:
+ with open(path, 'rb') as f:
+ while True:
+ buf = f.read(bs)
+ if not buf:
+ break
+ m.update(buf)
+ except Exception as e:
+ print(f'Failed to calculate checksum for {path}: {e}')
+ return None
+ return m.hexdigest()