2022-09-08 05:32:19 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright © 2020 - 2022 Collabora Ltd.
|
|
|
|
# Authors:
|
|
|
|
# Tomeu Vizoso <tomeu.vizoso@collabora.com>
|
|
|
|
# David Heidelberg <david.heidelberg@collabora.com>
|
2024-01-23 07:10:04 +08:00
|
|
|
# Guilherme Gallo <guilherme.gallo@collabora.com>
|
2022-09-08 05:32:19 +08:00
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
'''Shared functions between the scripts.'''
|
|
|
|
|
2024-01-29 18:51:10 +08:00
|
|
|
import logging
|
2022-09-08 05:32:19 +08:00
|
|
|
import os
|
2024-01-29 18:51:10 +08:00
|
|
|
import re
|
2022-09-08 05:32:19 +08:00
|
|
|
import time
|
2024-07-26 23:48:32 +08:00
|
|
|
from functools import cache
|
2024-01-23 07:10:04 +08:00
|
|
|
from pathlib import Path
|
2022-09-08 05:32:19 +08:00
|
|
|
|
2023-10-19 04:09:48 +08:00
|
|
|
GITLAB_URL = "https://gitlab.freedesktop.org"
|
2024-01-23 07:10:04 +08:00
|
|
|
TOKEN_DIR = Path(os.getenv("XDG_CONFIG_HOME") or Path.home() / ".config")
|
2023-10-19 04:09:48 +08:00
|
|
|
|
2024-01-29 18:51:10 +08:00
|
|
|
# Known GitLab token prefixes: https://docs.gitlab.com/ee/security/token_overview.html#token-prefixes
|
|
|
|
TOKEN_PREFIXES: dict[str, str] = {
|
|
|
|
"Personal access token": "glpat-",
|
|
|
|
"OAuth Application Secret": "gloas-",
|
|
|
|
"Deploy token": "gldt-",
|
|
|
|
"Runner authentication token": "glrt-",
|
|
|
|
"CI/CD Job token": "glcbt-",
|
|
|
|
"Trigger token": "glptt-",
|
|
|
|
"Feed token": "glft-",
|
|
|
|
"Incoming mail token": "glimt-",
|
|
|
|
"GitLab Agent for Kubernetes token": "glagent-",
|
2024-07-26 23:48:32 +08:00
|
|
|
"SCIM Tokens": "glsoat-",
|
2024-01-29 18:51:10 +08:00
|
|
|
}
|
|
|
|
|
2023-10-19 04:09:48 +08:00
|
|
|
|
2024-07-26 23:48:32 +08:00
|
|
|
@cache
|
|
|
|
def print_once(*args, **kwargs):
|
|
|
|
"""Print without spamming the output"""
|
|
|
|
print(*args, **kwargs)
|
|
|
|
|
|
|
|
|
2023-10-21 06:29:13 +08:00
|
|
|
def pretty_duration(seconds):
|
|
|
|
"""Pretty print duration"""
|
|
|
|
hours, rem = divmod(seconds, 3600)
|
|
|
|
minutes, seconds = divmod(rem, 60)
|
|
|
|
if hours:
|
2024-05-24 20:55:42 +08:00
|
|
|
return f"{hours:0.0f}h{minutes:02.0f}m{seconds:02.0f}s"
|
2023-10-21 06:29:13 +08:00
|
|
|
if minutes:
|
2024-05-24 20:55:42 +08:00
|
|
|
return f"{minutes:0.0f}m{seconds:02.0f}s"
|
2023-10-21 06:29:13 +08:00
|
|
|
return f"{seconds:0.0f}s"
|
|
|
|
|
|
|
|
|
2024-06-12 04:53:01 +08:00
|
|
|
def get_gitlab_pipeline_from_url(gl, pipeline_url) -> tuple:
|
|
|
|
"""
|
|
|
|
Extract the project and pipeline object from the url string
|
|
|
|
:param gl: Gitlab object
|
|
|
|
:param pipeline_url: string with a url to a pipeline
|
|
|
|
:return: ProjectPipeline, Project objects
|
|
|
|
"""
|
2024-06-13 22:29:05 +08:00
|
|
|
pattern = rf"^{re.escape(GITLAB_URL)}/(.*)/-/pipelines/([0-9]+)$"
|
2024-06-12 04:53:01 +08:00
|
|
|
match = re.match(pattern, pipeline_url)
|
|
|
|
if not match:
|
|
|
|
raise AssertionError(f"url {pipeline_url} doesn't follow the pattern {pattern}")
|
|
|
|
namespace_with_project, pipeline_id = match.groups()
|
|
|
|
cur_project = gl.projects.get(namespace_with_project)
|
2023-10-19 04:09:48 +08:00
|
|
|
pipe = cur_project.pipelines.get(pipeline_id)
|
|
|
|
return pipe, cur_project
|
|
|
|
|
|
|
|
|
2022-09-08 05:32:19 +08:00
|
|
|
def get_gitlab_project(glab, name: str):
|
|
|
|
"""Finds a specified gitlab project for given user"""
|
2023-09-30 09:41:58 +08:00
|
|
|
if "/" in name:
|
|
|
|
project_path = name
|
|
|
|
else:
|
|
|
|
glab.auth()
|
|
|
|
username = glab.user.username
|
|
|
|
project_path = f"{username}/{name}"
|
|
|
|
return glab.projects.get(project_path)
|
2022-09-08 05:32:19 +08:00
|
|
|
|
|
|
|
|
2024-01-23 07:10:04 +08:00
|
|
|
def get_token_from_default_dir() -> str:
|
|
|
|
"""
|
|
|
|
Retrieves the GitLab token from the default directory.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: The path to the GitLab token file.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
FileNotFoundError: If the token file is not found.
|
|
|
|
"""
|
|
|
|
token_file = TOKEN_DIR / "gitlab-token"
|
|
|
|
try:
|
|
|
|
return str(token_file.resolve())
|
|
|
|
except FileNotFoundError as ex:
|
|
|
|
print(
|
|
|
|
f"Could not find {token_file}, please provide a token file as an argument"
|
|
|
|
)
|
|
|
|
raise ex
|
|
|
|
|
|
|
|
|
2024-01-29 18:51:10 +08:00
|
|
|
def validate_gitlab_token(token: str) -> bool:
|
2024-09-20 07:29:55 +08:00
|
|
|
# Match against recognised token prefixes
|
|
|
|
token_suffix = None
|
|
|
|
for token_type, token_prefix in TOKEN_PREFIXES.items():
|
|
|
|
if token.startswith(token_prefix):
|
|
|
|
logging.info(f"Found probable token type: {token_type}")
|
|
|
|
token_suffix = token[len(token_prefix):]
|
|
|
|
break
|
|
|
|
|
|
|
|
if not token_suffix:
|
|
|
|
return False
|
|
|
|
|
2024-01-29 18:51:10 +08:00
|
|
|
# Basic validation of the token suffix based on:
|
|
|
|
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/gems/gitlab-secret_detection/lib/gitleaks.toml
|
|
|
|
if not re.match(r"(\w+-)?[0-9a-zA-Z_\-]{20,64}", token_suffix):
|
|
|
|
return False
|
2024-01-24 03:49:08 +08:00
|
|
|
|
2024-09-20 07:29:55 +08:00
|
|
|
return True
|
2024-01-29 18:51:10 +08:00
|
|
|
|
|
|
|
|
|
|
|
def get_token_from_arg(token_arg: str | Path | None) -> str | None:
|
|
|
|
if not token_arg:
|
|
|
|
logging.info("No token provided.")
|
|
|
|
return None
|
|
|
|
|
|
|
|
token_path = Path(token_arg)
|
|
|
|
if token_path.is_file():
|
|
|
|
return read_token_from_file(token_path)
|
|
|
|
|
|
|
|
return handle_direct_token(token_path, token_arg)
|
|
|
|
|
|
|
|
|
|
|
|
def read_token_from_file(token_path: Path) -> str:
|
|
|
|
token = token_path.read_text().strip()
|
|
|
|
logging.info(f"Token read from file: {token_path}")
|
|
|
|
return token
|
|
|
|
|
|
|
|
|
|
|
|
def handle_direct_token(token_path: Path, token_arg: str | Path) -> str | None:
|
|
|
|
if token_path == Path(get_token_from_default_dir()):
|
|
|
|
logging.warning(
|
|
|
|
f"The default token file {token_path} was not found. "
|
|
|
|
"Please provide a token file or a token directly via --token arg."
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
logging.info("Token provided directly as an argument.")
|
|
|
|
return str(token_arg)
|
|
|
|
|
|
|
|
|
|
|
|
def read_token(token_arg: str | Path | None) -> str | None:
|
|
|
|
token = get_token_from_arg(token_arg)
|
|
|
|
if token and not validate_gitlab_token(token):
|
|
|
|
logging.warning("The provided token is either an old token or does not seem to "
|
|
|
|
"be a valid token.")
|
|
|
|
logging.warning("Newer tokens are the ones created from a Gitlab 14.5+ instance.")
|
|
|
|
logging.warning("See https://about.gitlab.com/releases/2021/11/22/"
|
|
|
|
"gitlab-14-5-released/"
|
|
|
|
"#new-gitlab-access-token-prefix-and-detection")
|
|
|
|
return token
|
2022-09-08 05:32:19 +08:00
|
|
|
|
|
|
|
|
2023-10-19 18:03:34 +08:00
|
|
|
def wait_for_pipeline(projects, sha: str, timeout=None):
|
2022-09-08 05:32:19 +08:00
|
|
|
"""await until pipeline appears in Gitlab"""
|
2023-10-19 18:03:34 +08:00
|
|
|
project_names = [project.path_with_namespace for project in projects]
|
|
|
|
print(f"⏲ for the pipeline to appear in {project_names}..", end="")
|
2023-09-11 21:58:17 +08:00
|
|
|
start_time = time.time()
|
2022-09-08 05:32:19 +08:00
|
|
|
while True:
|
2023-10-19 18:03:34 +08:00
|
|
|
for project in projects:
|
|
|
|
pipelines = project.pipelines.list(sha=sha)
|
|
|
|
if pipelines:
|
|
|
|
print("", flush=True)
|
|
|
|
return (pipelines[0], project)
|
2022-09-08 05:32:19 +08:00
|
|
|
print("", end=".", flush=True)
|
2023-09-11 21:58:17 +08:00
|
|
|
if timeout and time.time() - start_time > timeout:
|
|
|
|
print(" not found", flush=True)
|
2023-10-19 18:03:34 +08:00
|
|
|
return (None, None)
|
2022-09-08 05:32:19 +08:00
|
|
|
time.sleep(1)
|