Compare commits

...

7 Commits

Author SHA1 Message Date
Vadim vtroshchinskiy d4ce9c3ee3 Make branch deletion RESTful 2025-02-06 16:22:38 +01:00
Vadim vtroshchinskiy 8bebeb619a Branch deletion 2025-02-06 16:14:17 +01:00
Vadim vtroshchinskiy 115df98905 Log every request 2025-02-06 16:03:23 +01:00
Vadim vtroshchinskiy 5721e56237 Rework the ability to use a custom SSH key
The code wasn't up to date with the Forgejo changes
2025-02-06 15:31:37 +01:00
Vadim vtroshchinskiy 3ebc728fb9 Mark git repo as a safe directory
Fixes problems due to git not liking the ownership
2025-02-06 13:15:21 +01:00
Vadim vtroshchinskiy 46732216eb More error logging 2025-02-06 13:14:53 +01:00
Vadim vtroshchinskiy 1f2095ce1a Improve task management, cleanup when there are too many 2025-02-06 13:14:31 +01:00
2 changed files with 116 additions and 54 deletions

View File

@ -77,7 +77,7 @@ REPOSITORIES_BASE_PATH = "/opt/opengnsys/ogrepository/oggit/git/oggit/"
start_time = time.time()
tasks = {}
tasks_max = 1024
# Create an instance of the Flask class
app = Flask(__name__)
@ -93,6 +93,24 @@ executor = Executor(app)
def add_task(future):
task_id = uuid.uuid4().hex
task_data = {
"future" : future,
"start_time" : time.time()
}
while len(tasks) >= tasks_max:
oldest_task_id = min(tasks, key=lambda k: tasks[k]['start_time'])
task = tasks[task_id]["future"]
if task.running():
log.error("Cancelling still running task %s, maximum task limit of %i reached", task_id, tasks_max)
task.cancel()
del tasks[oldest_task_id]
tasks[task_id] = task_data
return task_id
def do_repo_backup(repo, params):
"""
@ -110,7 +128,10 @@ def do_repo_backup(repo, params):
bool: True if the backup was successful.
"""
gitrepo = git.Repo(f"{REPOSITORIES_BASE_PATH}/{repo}.git")
git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git"
git_repo = git.Repo(git_repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@ -120,7 +141,7 @@ def do_repo_backup(repo, params):
with sftp.file(params["filename"], mode='wb+') as remote_file:
gitrepo.archive(remote_file, format="tar.gz")
git_repo.archive(remote_file, format="tar.gz")
return True
@ -139,13 +160,16 @@ def do_repo_sync(repo, params):
- "remote_ref" (str): The name of the remote reference.
- "summary" (str): A summary of the push operation for the reference.
"""
gitrepo = git.Repo(f"{REPOSITORIES_BASE_PATH}/{repo}.git")
git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git"
git_repo = git.Repo(git_repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
# Recreate the remote every time, it might change
if "backup" in gitrepo.remotes:
gitrepo.delete_remote("backup")
if "backup" in git_repo.remotes:
git_repo.delete_remote("backup")
backup_repo = gitrepo.create_remote("backup", params["remote_repository"])
backup_repo = git_repo.create_remote("backup", params["remote_repository"])
pushed_references = backup_repo.push("*:*")
results = []
@ -165,11 +189,11 @@ def do_repo_gc(repo):
Returns:
bool: True if the garbage collection command was executed successfully.
"""
gitrepo = git.Repo(f"{REPOSITORIES_BASE_PATH}/{repo}.git")
gitrepo.git.gc()
return True
git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
git_repo = git.Repo(git_repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
git_repo.git.gc()
@app.errorhandler(HTTPException)
def handle_exception(e):
@ -279,6 +303,7 @@ class GitRepositories(Resource):
data = request.json
if data is None:
log.error("Can't create repository, JSON post data missing")
return {"error" : "Parameters missing"}, 400
repo = data["name"]
@ -325,15 +350,16 @@ class GitRepoSync(Resource):
data = request.json
if data is None:
log.error("Can't create repository, JSON post data missing")
return {"error" : "Parameters missing"}, 400
if not "remote_repository" in data:
log.error("Can't create repository, parameter 'remote_repository' missing")
return {"error" : "Parameter 'remote_repository' missing"}, 400
future = executor.submit(do_repo_sync, repo, data)
task_id = str(uuid.uuid4())
tasks[task_id] = future
task_id = add_task(future)
log.info("Starting synchronization of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id})
return {"status": "started", "task_id" : task_id}, 200
@ -373,6 +399,7 @@ class GitRepoBackup(Resource):
data = request.json
if data is None:
log.error("Can't create repository, JSON post data missing")
return {"error" : "Parameters missing"}, 400
@ -381,8 +408,7 @@ class GitRepoBackup(Resource):
future = executor.submit(do_repo_backup, repo, data)
task_id = str(uuid.uuid4())
tasks[task_id] = future
task_id = add_task(future)
log.info("Starting backup of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id})
return {"status": "started", "task_id" : task_id}, 200
@ -411,8 +437,7 @@ class GitRepoCompact(Resource):
return {"error": "Repository not found"}, 404
future = executor.submit(do_repo_gc, repo)
task_id = str(uuid.uuid4())
tasks[task_id] = future
task_id = add_task(future)
log.info("Starting compaction of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id})
return {"status": "started", "task_id" : task_id}, 200
@ -437,7 +462,7 @@ class GitTaskStatus(Resource):
log.error("Task %s was not found", task_id, extra = {"task_id" : task_id})
return {"error": "Task not found"}, 404
future = tasks[task_id]
future = tasks[task_id]["future"]
try:
if future.done():
@ -506,6 +531,8 @@ class GitRepoBranches(Resource):
return {"error": "Repository not found"}, 404
git_repo = git.Repo(repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', repo_path)
branches = []
for branch in git_repo.branches:
@ -516,7 +543,36 @@ class GitRepoBranches(Resource):
"branches": branches
}
@git_ns.route('/repositories/<repo>/branches/<branch>')
class GitRepoBranchesDeleter(Resource):
def delete(self, repo, branch):
"""Delete a given branch in a given repository
Args:
repo (str): The name of the repository.
Returns:
Response: A JSON response containing a list of branch names or an error message if the repository is not found.
- 200: A JSON object with a "status" key containing "deleted"
- 404: A JSON object with an "error" key containing the message "Repository not found" or "Branch not found"
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
return {"error": "Repository not found"}, 404
git_repo = git.Repo(repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', repo_path)
if not branch in git_repo.branches:
log.error("Can't delete branch %s, not found in repository %s", branch, repo, extra = {"repository" : repo, "branch" : branch})
return {"error": "Branch not found"}, 404
git_repo.delete_head(branch)
log.info("Branch %s of repository %s deleted", branch, repo, extra = {"repository" : repo, "branch" : branch})
return {"status": "deleted"}, 200
@git_ns.route('/health')
@ -557,6 +613,15 @@ class GitStatus(Resource):
"active_tasks" : len(tasks)
}
@app.after_request
def after_request(response):
log.info("Request from %s: %s %s %s %s", request.remote_addr, request.method, request.scheme, request.full_path, response.status,
extra = {"remote_addr" : request.remote_addr, "method" : request.method, "scheme" : request.scheme, "full_path" : request.full_path, "status" : response.status})
if debug_enabled:
log.debug("Response: %s", response.data, extra = {"response" : response.data})
return response
api.add_namespace(git_ns)

View File

@ -248,10 +248,6 @@ class OpengnsysGitInstaller:
"""Ignorar requisito de clave de ssh para el instalador"""
self.ignoresshkey = value
def set_usesshkey(self, value):
"""Usar clave de ssh especificada"""
self.usesshkey = value
def set_basepath(self, value):
"""Establece ruta base de OpenGnsys
Valor por defecto: /opt/opengnsys
@ -478,38 +474,33 @@ class OpengnsysGitInstaller:
self.__logger.debug("Using provided initrd file %s", initrd_file)
client_initrd_path = initrd_file
if self.usesshkey:
with open(self.usesshkey, 'r') as f:
public_key = f.read().strip()
self.__logger.debug("Extracting key from %s", client_initrd_path)
if os.path.isfile(client_initrd_path):
#os.makedirs(temp_dir, exist_ok=True)
#os.chdir(self.temp_dir.name)
self.__logger.debug("Uncompressing %s", client_initrd_path)
public_key = None
with libarchive.file_reader(client_initrd_path) as initrd:
for file in initrd:
self.__logger.debug("File: %s", file)
pathname = file.pathname;
if pathname.startswith("./"):
pathname = pathname[2:]
if pathname in self.key_paths_dict:
self.__logger.info("Found key %s, extracting", pathname)
data = bytearray()
for block in file.get_blocks():
data = data + block
public_key = data.decode('utf-8').strip()
break
else:
self.__logger.debug("Extracting key from %s", client_initrd_path)
if os.path.isfile(client_initrd_path):
#os.makedirs(temp_dir, exist_ok=True)
#os.chdir(self.temp_dir.name)
self.__logger.debug("Uncompressing %s", client_initrd_path)
public_key = None
with libarchive.file_reader(client_initrd_path) as initrd:
for file in initrd:
self.__logger.debug("File: %s", file)
pathname = file.pathname;
if pathname.startswith("./"):
pathname = pathname[2:]
if pathname in self.key_paths_dict:
self.__logger.info("Found key %s, extracting", pathname)
data = bytearray()
for block in file.get_blocks():
data = data + block
public_key = data.decode('utf-8').strip()
break
else:
print(f"Failed to find initrd at {client_initrd_path}")
exit(2)
print(f"Failed to find initrd at {client_initrd_path}")
exit(2)
if not public_key:
self.__logger.warning("Failed to find a SSH key")
@ -961,7 +952,7 @@ if __name__ == '__main__':
parser.add_argument('--testmode', action='store_true', help="Modo de prueba")
parser.add_argument('--ignoresshkey', action='store_true', help="Ignorar clave de SSH")
parser.add_argument('--usesshkey', type=str, help="Usar clave SSH especificada")
parser.add_argument('--use-ssh-key', metavar="FILE", type=str, help="Add the SSH key from the specified file")
parser.add_argument('--test-createuser', action='store_true')
parser.add_argument('--extract-ssh-key', action='store_true', help="Extract SSH key from oglive squashfs")
parser.add_argument('--set-ssh-key', action='store_true', help="Read SSH key from oglive squashfs and set it in Forgejo")
@ -993,7 +984,6 @@ if __name__ == '__main__':
installer = OpengnsysGitInstaller()
installer.set_testmode(args.testmode)
installer.set_ignoresshkey(args.ignoresshkey)
installer.set_usesshkey(args.usesshkey)
logger.debug("Inicio de instalación")
@ -1012,6 +1002,13 @@ if __name__ == '__main__':
print(f"{key}")
elif args.set_ssh_key:
installer.add_ssh_key_from_squashfs(oglive_num=args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url)
elif args.use_ssh_key:
with open(args.use_ssh_key, 'r', encoding='utf-8') as ssh_key_file:
ssh_key_data = ssh_key_file.read().strip()
(keytype, keydata, description) = ssh_key_data.split(" ", 2)
installer.add_forgejo_sshkey(f"{keytype} {keydata}", description)
elif args.set_ssh_key_in_initrd:
installer.set_ssh_key_in_initrd()
elif args.get_image_paths: