1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
#!/usr/bin/env python3
# Copyright © 2020 - 2022 Collabora Ltd.
# Authors:
# Tomeu Vizoso <tomeu.vizoso@collabora.com>
# David Heidelberg <david.heidelberg@collabora.com>
# Guilherme Gallo <guilherme.gallo@collabora.com>
#
# SPDX-License-Identifier: MIT
'''Shared functions between the scripts.'''
import logging
import os
import re
import time
from functools import cache
from pathlib import Path
GITLAB_URL = "https://gitlab.freedesktop.org"
TOKEN_DIR = Path(os.environ.get("XDG_CONFIG_HOME", "")
if os.environ.get("XDG_CONFIG_HOME", None)
else Path.home() / ".config")
# Known GitLab token prefixes: https://docs.gitlab.com/security/tokens/#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-",
"SCIM Tokens": "glsoat-",
}
@cache
def print_once(*args, **kwargs):
"""Print without spamming the output"""
print(*args, **kwargs)
def pretty_duration(seconds):
"""Pretty print duration"""
hours, rem = divmod(seconds, 3600)
minutes, seconds = divmod(rem, 60)
if hours:
return f"{hours:0.0f}h{minutes:02.0f}m{seconds:02.0f}s"
if minutes:
return f"{minutes:0.0f}m{seconds:02.0f}s"
return f"{seconds:0.0f}s"
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
"""
pattern = rf"^{re.escape(GITLAB_URL)}/(.*)/-/pipelines/([0-9]+)$"
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)
pipe = cur_project.pipelines.get(pipeline_id)
return pipe, cur_project
def get_gitlab_project(glab, name: str):
"""Finds a specified gitlab project for given user"""
if "/" in name:
project_path = name
else:
glab.auth()
username = glab.user.username
project_path = f"{username}/{name}"
return glab.projects.get(project_path)
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
def validate_gitlab_token(token: str) -> bool:
# 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
# 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
return True
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
def wait_for_pipeline(projects, sha: str, timeout=None):
"""await until pipeline appears in Gitlab"""
project_names = [project.path_with_namespace for project in projects]
print(f"⏲ for the pipeline to appear in {project_names}..", end="")
start_time = time.time()
while True:
for project in projects:
pipelines = project.pipelines.list(sha=sha)
if pipelines:
print("", flush=True)
return (pipelines[0], project)
print("", end=".", flush=True)
if timeout and time.time() - start_time > timeout:
print(" not found", flush=True)
return (None, None)
time.sleep(1)
|