diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..514946799d491022a81b8341b031214239aedf80 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +certifi==2025.1.31 +charset-normalizer==3.4.1 +idna==3.10 +python-gitlab==5.6.0 +requests==2.32.3 +requests-toolbelt==1.0.0 +urllib3==2.4.0 diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6a669e095b9a17aab97a1f638d4169d23ef47c15 --- /dev/null +++ b/utils/README.md @@ -0,0 +1,27 @@ +### Description +These utility scripts avoid copying each ci variable manually which is tedious. +- The gitlab-ci-vars-reader.py reads variables from a specific project or a pipeline (depending on the options provided) and copies them into a yaml file +- The gitlab-ci-vars-updater.py takes a yaml file containing key value pairs in yaml format as an input. It then creates/updates project variables or pipeline variables (depending on the options provided) + +### Prerequisites +``` +python -m venv ~/venvs/gitlab +source ~/venvs/gitlab/bin/activate +pip install -r requirements +``` + +### Setup +``` +cd utils +mv gitlab.ini.example gitlab.ini +``` +Make changes to the gitlab.ini as you require. +[Create a personal access token](https://docs.gitlab.com/user/profile/personal_access_tokens/) via the gitlab UI and copy it to the private_token field in gitlab.ini file + +### Usage +> Create an empty schedule pipeline before you try this out. +``` +python3 gitlab-ci-vars-reader.py --config_file gitlab.ini --project_id <PROJECT_ID> --sched_pipeline_id <PIPELINE_ID> --var_file ci-variables.yaml + +python3 gitlab-ci-vars-updater.py --config_file gitlab.ini --project_id <PROJECT_ID> --sched_pipeline_id <NEW-PIPELINE_ID> --var_file ci-variables.yaml +``` diff --git a/utils/gitlab-ci-vars-reader.py b/utils/gitlab-ci-vars-reader.py new file mode 100755 index 0000000000000000000000000000000000000000..c573a560a3131d0b5f9d8265c78869fe46d08c0f --- /dev/null +++ b/utils/gitlab-ci-vars-reader.py @@ -0,0 +1,72 @@ +import argparse + +import gitlab +import yaml + + +# Function to fetch all CI/CD variables from a GitLab project +def fetch_variables(project): + p_variables = list(project.variables.list(iterator=True)) + variables = [var.asdict() for var in p_variables] + + return variables + + +def fetch_sched_variables(sched_pipeline): + variables = sched_pipeline.attributes["variables"] + return variables + + +# Main function to load the config and fetch variables +def main(): + # Setup argument parser + parser = argparse.ArgumentParser(description="GitLab CI/CD Variable reader") + parser.add_argument( + "--config_file", + type=str, + default="gitlab.ini", + required=True, + help="Path to the configuration file (default: gitlab.ini)", + ) + parser.add_argument( + "--var_file", + type=str, + default="ci-variables.yaml", + help="Path to the CI vars file (default: ci-variables.yaml)", + ) + parser.add_argument( + "--project_name", + type=str, + required=True, + help="Gitlab project name with namespace", + ) + parser.add_argument( + "--sched_pipeline_id", + type=int, + help="Gitlab project scheduled pipeline ID", + ) + + # Parse the arguments + args = parser.parse_args() + + gl = gitlab.Gitlab.from_config("uabrc", [args.config_file]) + project = gl.projects.get(args.project_name) + + # Fetch project or sched pipeline variables + if not args.sched_pipeline_id: + variables = fetch_variables(project) + else: + sched_pipeline = project.pipelineschedules.get(args.sched_pipeline_id) + variables = fetch_sched_variables(sched_pipeline) + + try: + with open(args.var_file, mode="wt", encoding="utf-8") as file: + yaml.dump(variables, file, explicit_start=True) + except FileNotFoundError: + print(f"Error: Writing File to '{args.var_file}'") + exit(1) + + +# Run the main function +if __name__ == "__main__": + main() diff --git a/utils/gitlab-ci-vars-updater.py b/utils/gitlab-ci-vars-updater.py new file mode 100644 index 0000000000000000000000000000000000000000..5da49d5f5627043306e8f9257e19adfc605d3b65 --- /dev/null +++ b/utils/gitlab-ci-vars-updater.py @@ -0,0 +1,133 @@ +import argparse + +import gitlab +import yaml + + +def load_file(file_path): + try: + with open(file_path, mode="rt", encoding="utf-8") as file: + return yaml.safe_load(file) + except FileNotFoundError: + print(f"Error: Configuration file '{file_path}' not found.") + exit(1) + + +# Function to create or update a GitLab CI/CD variable +def create_or_update_variable(project, var_dict): + key = var_dict.get("key") + scope = var_dict.get("environment_scope", "*") + p_variable = None + + DEFAULTS = { + "variable_type": "env_var", + "hidden": False, + "protected": False, + "masked": False, + "environment_scope": "*", + "raw": False, + "description": None, + } + + # Merge defaults with var_dict + var_dict = {**DEFAULTS, **var_dict} + + # Fetch a variable with matching key and scope + try: + all_vars = project.variables.list(get_all=True) + for var in all_vars: + if var.key == key and var.environment_scope == scope: + p_variable = var + break + except gitlab.exceptions.GitlabGetError: + print("Variable not found") + exit(1) + + # Check if the variable exists and same as input + if p_variable is not None: + if p_variable.asdict() != var_dict: + # if not same update the project variable + print(f"Updating {p_variable.attributes['key']}") + p_variable.delete() + return project.variables.create(var_dict) + else: + print(f"variable {var_dict["key"]} already exists") + # Create variable if it doesn't exist in the project + else: + print(f"Creating variable {var_dict["key"]}") + return project.variables.create(var_dict) + + +def get_pipeline_vars_by_key(sched_pipeline, key_name): + p_vars = sched_pipeline.attributes["variables"] + for p_variable in p_vars: + if p_variable.get("key") == key_name: + return p_variable + + +# Function to create or update a schedule pipeline variable +def create_or_update_sched_vars(sched_pipeline, var_dict): + # Check if the variable exists in the sched pipeline + p_variable = get_pipeline_vars_by_key(sched_pipeline, var_dict["key"]) + if p_variable: + # Check if the attributes are the same + if p_variable != var_dict: + # If not update the value in the project + sched_pipeline.variables.delete(p_variable["key"]) + sched_pipeline.variables.create(var_dict) + else: + print(f"variable {var_dict["key"]} already exists") + # Create variable if it doesn't exist in the project + else: + print(f"Creating variable {var_dict["key"]}") + return sched_pipeline.variables.create(var_dict) + + +def main(): + # Setup argument parser + parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Updater") + parser.add_argument( + "--config_file", + type=str, + default="gitlab.ini", + required=True, + help="Path to the configuration file (default: gitlab.ini)", + ) + parser.add_argument( + "--var_file", + type=str, + default="ci-variables.yaml", + help="Path to the CI vars file (default: ci-variables.yaml)", + ) + parser.add_argument( + "--project_name", + type=str, + required=True, + help="Gitlab project name with namespace", + ) + parser.add_argument( + "--sched_pipeline_id", + type=int, + help="Gitlab project scheduled pipeline ID", + ) + + # Parse the arguments + args = parser.parse_args() + + gl = gitlab.Gitlab.from_config("uabrc", [args.config_file]) + project = gl.projects.get(args.project_name) + + # Load the CI vars file + var_list = load_file(args.var_file) + + # Create or update all variables + for var_dict in var_list: + if not args.sched_pipeline_id: + create_or_update_variable(project, var_dict) + else: + sched_pipeline = project.pipelineschedules.get(args.sched_pipeline_id) + create_or_update_sched_vars(sched_pipeline, var_dict) + + +if __name__ == "__main__": + main() diff --git a/utils/gitlab.ini.example b/utils/gitlab.ini.example new file mode 100644 index 0000000000000000000000000000000000000000..6b211534ed694e74c6127e94f810dee8bf2879c0 --- /dev/null +++ b/utils/gitlab.ini.example @@ -0,0 +1,10 @@ +[global] +default = uabrc +ssl_verify = true +timeout = 5 +per_page = 100 + +[uabrc] +url = https://gitlab.rc.uab.edu +private_token = +api_version = 4