From aa196497b41c9d8bda155e2b3f369010fd52d5f1 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Mon, 28 Oct 2024 16:49:13 -0400
Subject: [PATCH 01/18] feat: Add scripts to read and write the cicd vars

---
 config.json               |  5 +++
 gitlab-ci-vars-reader.py  | 71 +++++++++++++++++++++++++++++++++
 gitlab-ci-vars-updater.py | 84 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 160 insertions(+)
 create mode 100644 config.json
 create mode 100755 gitlab-ci-vars-reader.py
 create mode 100644 gitlab-ci-vars-updater.py

diff --git a/config.json b/config.json
new file mode 100644
index 0000000..9965336
--- /dev/null
+++ b/config.json
@@ -0,0 +1,5 @@
+{
+    "gitlab_url": "",
+    "private_token": "",
+    "project_id": ""
+}
\ No newline at end of file
diff --git a/gitlab-ci-vars-reader.py b/gitlab-ci-vars-reader.py
new file mode 100755
index 0000000..8894dd3
--- /dev/null
+++ b/gitlab-ci-vars-reader.py
@@ -0,0 +1,71 @@
+import requests
+import json
+import argparse
+
+# Load configuration from JSON file
+def load_config(file_path):
+    try:
+        with open(file_path, 'r') as file:
+            return json.load(file)
+    except FileNotFoundError:
+        print(f"Error: Configuration file '{file_path}' not found.")
+        exit(1)
+
+# Function to fetch all CI/CD variables from a GitLab project
+def fetch_variables(config):
+    gitlab_url = config["gitlab_url"]
+    project_id = config["project_id"]
+    private_token = config["private_token"]
+
+    headers = {
+        "PRIVATE-TOKEN": private_token,
+        "Content-Type": "application/json"
+    }
+
+    url = f"{gitlab_url}/api/v4/projects/{project_id}/variables"
+    response = requests.get(url, headers=headers)
+
+    if response.status_code == 200:
+        variables = response.json()
+        return variables
+    else:
+        print(f"Failed to fetch variables. Status code: {response.status_code}")
+        return []
+
+# Function to format the fetched variables for input into the config file
+def format_variables_for_input(variables):
+    ci_variables = {var['key']: var['value'] for var in variables}
+    return ci_variables
+
+# Main function to load the config and fetch variables
+def main():
+    # Setup argument parser
+    parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Reader")
+    parser.add_argument(
+        "--config",
+        type=str,
+        default="config.json",
+        help="Path to the configuration file (default: config.json)"
+    )
+
+    # Parse the arguments
+    args = parser.parse_args()
+
+    # Load the configuration file
+    config = load_config(args.config)
+
+    # Fetch and print the variables
+    variables = fetch_variables(config)
+
+    if variables:
+        print("Fetched variables:")
+        formatted_variables = format_variables_for_input(variables)
+        # Print the formatted variables dictionary in JSON format
+        print(json.dumps(formatted_variables, indent=4))
+    else:
+        print("No variables found.")
+
+# Run the main function
+if __name__ == "__main__":
+    main()
+
diff --git a/gitlab-ci-vars-updater.py b/gitlab-ci-vars-updater.py
new file mode 100644
index 0000000..d400669
--- /dev/null
+++ b/gitlab-ci-vars-updater.py
@@ -0,0 +1,84 @@
+import requests
+import json
+import argparse
+
+# Load configuration from JSON file
+def load_config(file_path):
+    try:
+        with open(file_path, 'r') as file:
+            return json.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(config, var_name, var_value):
+    gitlab_url = config["gitlab_url"]
+    project_id = config["project_id"]
+    private_token = config["private_token"]
+
+    headers = {
+        "PRIVATE-TOKEN": private_token,
+        "Content-Type": "application/json"
+    }
+
+    url = f"{gitlab_url}/api/v4/projects/{project_id}/variables/{var_name}"
+
+    # Check if the variable already exists
+    response = requests.get(url, headers=headers)
+
+    if response.status_code == 404:
+        print(f"Variable '{var_name}' does not exist. Creating new variable...")
+        create_url = f"{gitlab_url}/api/v4/projects/{project_id}/variables"
+        data = {
+            "key": var_name,
+            "value": var_value,
+            "variable_type": "env_var",  # Can be 'file' for file variables
+            "protected": False,  # Set to True if you want the variable protected
+            "masked": False  # Set to True if you want the variable masked
+        }
+        create_response = requests.post(create_url, headers=headers, json=data)
+
+        if create_response.status_code == 201:
+            print(f"Variable '{var_name}' created successfully.")
+        else:
+            print(f"Failed to create variable '{var_name}'. Status code: {create_response.status_code}")
+    elif response.status_code == 200:
+        print(f"Variable '{var_name}' already exists. Updating variable...")
+        data = {"value": var_value}
+        update_response = requests.put(url, headers=headers, json=data)
+
+        if update_response.status_code == 200:
+            print(f"Variable '{var_name}' updated successfully.")
+        else:
+            print(f"Failed to update variable '{var_name}'. Status code: {update_response.status_code}")
+    else:
+        print(f"Error checking variable '{var_name}'. Status code: {response.status_code}")
+
+# Main function to load the config and apply variables
+def main():
+    # Setup argument parser
+    parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Updater")
+    parser.add_argument(
+        "--config",
+        type=str,
+        default="config.json",
+        help="Path to the configuration file (default: config.json)"
+    )
+
+    # Parse the arguments
+    args = parser.parse_args()
+
+    # Load the configuration file
+    config = load_config(args.config)
+
+    ci_variables = config["ci_variables"]
+
+    # Loop through and create/update all variables
+    for var_name, var_value in ci_variables.items():
+        create_or_update_variable(config, var_name, var_value)
+
+# Run the main function
+if __name__ == "__main__":
+    main()
+
-- 
GitLab


From 51aed5aa787a649305cfb9f668aa40c9936a763c Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Mon, 28 Oct 2024 23:33:34 -0400
Subject: [PATCH 02/18] feat: Separate auth config and vars file

---
 gitlab-ci-vars-reader.py  | 12 ++++++------
 gitlab-ci-vars-updater.py | 19 +++++++++++++------
 2 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/gitlab-ci-vars-reader.py b/gitlab-ci-vars-reader.py
index 8894dd3..6722606 100755
--- a/gitlab-ci-vars-reader.py
+++ b/gitlab-ci-vars-reader.py
@@ -14,7 +14,7 @@ def load_config(file_path):
 # Function to fetch all CI/CD variables from a GitLab project
 def fetch_variables(config):
     gitlab_url = config["gitlab_url"]
-    project_id = config["project_id"]
+    project_id = config["src_project_id"]
     private_token = config["private_token"]
 
     headers = {
@@ -22,7 +22,7 @@ def fetch_variables(config):
         "Content-Type": "application/json"
     }
 
-    url = f"{gitlab_url}/api/v4/projects/{project_id}/variables"
+    url = f"{gitlab_url}/api/v4/projects/{project_id}/variables?per_page=100"
     response = requests.get(url, headers=headers)
 
     if response.status_code == 200:
@@ -42,9 +42,9 @@ def main():
     # Setup argument parser
     parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Reader")
     parser.add_argument(
-        "--config",
+        "--auth_config",
         type=str,
-        default="config.json",
+        default="auth-config.json",
         help="Path to the configuration file (default: config.json)"
     )
 
@@ -52,13 +52,13 @@ def main():
     args = parser.parse_args()
 
     # Load the configuration file
-    config = load_config(args.config)
+    config = load_config(args.auth_config)
 
     # Fetch and print the variables
     variables = fetch_variables(config)
 
     if variables:
-        print("Fetched variables:")
+        print("ci_variables:")
         formatted_variables = format_variables_for_input(variables)
         # Print the formatted variables dictionary in JSON format
         print(json.dumps(formatted_variables, indent=4))
diff --git a/gitlab-ci-vars-updater.py b/gitlab-ci-vars-updater.py
index d400669..5e51a25 100644
--- a/gitlab-ci-vars-updater.py
+++ b/gitlab-ci-vars-updater.py
@@ -14,7 +14,7 @@ def load_config(file_path):
 # Function to create or update a GitLab CI/CD variable
 def create_or_update_variable(config, var_name, var_value):
     gitlab_url = config["gitlab_url"]
-    project_id = config["project_id"]
+    project_id = config["dest_project_id"]
     private_token = config["private_token"]
 
     headers = {
@@ -55,14 +55,20 @@ def create_or_update_variable(config, var_name, var_value):
     else:
         print(f"Error checking variable '{var_name}'. Status code: {response.status_code}")
 
-# Main function to load the config and apply variables
+# Main function to load the auth config and apply variables
 def main():
     # Setup argument parser
     parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Updater")
     parser.add_argument(
-        "--config",
+        "--auth_config",
         type=str,
-        default="config.json",
+        default="auth-config.json",
+        help="Path to the configuration file (default: config.json)"
+    )
+    parser.add_argument(
+        "--vars",
+        type=str,
+        default="ci-variables.json",
         help="Path to the configuration file (default: config.json)"
     )
 
@@ -70,9 +76,10 @@ def main():
     args = parser.parse_args()
 
     # Load the configuration file
-    config = load_config(args.config)
+    config = load_config(args.auth_config)
+    vars_dict = load_config(args.vars)
 
-    ci_variables = config["ci_variables"]
+    ci_variables = vars_dict["ci_variables"]
 
     # Loop through and create/update all variables
     for var_name, var_value in ci_variables.items():
-- 
GitLab


From 14abc939459baa94de0818a0e00b257d576b0b20 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Mon, 28 Oct 2024 23:38:06 -0400
Subject: [PATCH 03/18] feat: Move files to a dedicated dir

---
 gitlab-ci-vars-reader.py => scripts/gitlab-ci-vars-reader.py   | 0
 gitlab-ci-vars-updater.py => scripts/gitlab-ci-vars-updater.py | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename gitlab-ci-vars-reader.py => scripts/gitlab-ci-vars-reader.py (100%)
 rename gitlab-ci-vars-updater.py => scripts/gitlab-ci-vars-updater.py (100%)

diff --git a/gitlab-ci-vars-reader.py b/scripts/gitlab-ci-vars-reader.py
similarity index 100%
rename from gitlab-ci-vars-reader.py
rename to scripts/gitlab-ci-vars-reader.py
diff --git a/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
similarity index 100%
rename from gitlab-ci-vars-updater.py
rename to scripts/gitlab-ci-vars-updater.py
-- 
GitLab


From fac0002a75bde3356dfae4330b66fa96a4a57777 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Mon, 28 Apr 2025 19:54:00 -0400
Subject: [PATCH 04/18] feat: Use gitlab module vs requests to modify cicd vars

---
 config.json                       |   5 --
 scripts/gitlab-ci-vars-reader.py  |  94 ++++++++++----------
 scripts/gitlab-ci-vars-updater.py | 142 +++++++++++++++++-------------
 3 files changed, 131 insertions(+), 110 deletions(-)
 delete mode 100644 config.json

diff --git a/config.json b/config.json
deleted file mode 100644
index 9965336..0000000
--- a/config.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "gitlab_url": "",
-    "private_token": "",
-    "project_id": ""
-}
\ No newline at end of file
diff --git a/scripts/gitlab-ci-vars-reader.py b/scripts/gitlab-ci-vars-reader.py
index 6722606..9e17ae9 100755
--- a/scripts/gitlab-ci-vars-reader.py
+++ b/scripts/gitlab-ci-vars-reader.py
@@ -1,71 +1,73 @@
-import requests
-import json
 import argparse
+import json
+
+import gitlab
 
-# Load configuration from JSON file
-def load_config(file_path):
-    try:
-        with open(file_path, 'r') as file:
-            return json.load(file)
-    except FileNotFoundError:
-        print(f"Error: Configuration file '{file_path}' not found.")
-        exit(1)
 
 # Function to fetch all CI/CD variables from a GitLab project
-def fetch_variables(config):
-    gitlab_url = config["gitlab_url"]
-    project_id = config["src_project_id"]
-    private_token = config["private_token"]
+def fetch_variables(project):
+    p_variables = list(project.variables.list(iterator=True))
+    variables = [var.asdict() for var in p_variables]
 
-    headers = {
-        "PRIVATE-TOKEN": private_token,
-        "Content-Type": "application/json"
-    }
+    return variables
 
-    url = f"{gitlab_url}/api/v4/projects/{project_id}/variables?per_page=100"
-    response = requests.get(url, headers=headers)
 
-    if response.status_code == 200:
-        variables = response.json()
-        return variables
-    else:
-        print(f"Failed to fetch variables. Status code: {response.status_code}")
-        return []
+def fetch_sched_variables(sched_pipeline):
+    variables = sched_pipeline.attributes["variables"]
+    return variables
 
-# Function to format the fetched variables for input into the config file
-def format_variables_for_input(variables):
-    ci_variables = {var['key']: var['value'] for var in variables}
-    return ci_variables
 
 # Main function to load the config and fetch variables
 def main():
     # Setup argument parser
-    parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Reader")
+    parser = argparse.ArgumentParser(description="GitLab CI/CD Variable reader")
     parser.add_argument(
-        "--auth_config",
+        "--config_file",
         type=str,
-        default="auth-config.json",
-        help="Path to the configuration file (default: config.json)"
+        default="gitlab.ini",
+        required=True,
+        help="Path to the configuration file (default: gitlab.ini)",
+    )
+    parser.add_argument(
+        "--var_file",
+        type=str,
+        default="ci-variables.json",
+        help="Path to the CI vars file (default: ci-variables.json)",
+    )
+    parser.add_argument(
+        "--project_id",
+        type=int,
+        default="",
+        required=True,
+        help="Gitlab project ID to read variables from",
+    )
+    parser.add_argument(
+        "--sched_pipeline_id",
+        type=int,
+        help="Gitlab project scheduled pipeline ID",
     )
 
     # Parse the arguments
     args = parser.parse_args()
 
-    # Load the configuration file
-    config = load_config(args.auth_config)
+    gl = gitlab.Gitlab.from_config("uabrc", [args.config_file])
+    project = gl.projects.get(args.project_id)
 
-    # Fetch and print the variables
-    variables = fetch_variables(config)
-
-    if variables:
-        print("ci_variables:")
-        formatted_variables = format_variables_for_input(variables)
-        # Print the formatted variables dictionary in JSON format
-        print(json.dumps(formatted_variables, indent=4))
+    # Fetch project or sched pipeline variables
+    if not args.sched_pipeline_id:
+        variables = fetch_variables(project)
     else:
-        print("No variables found.")
+        sched_pipeline = project.pipelineschedules.get(args.sched_pipeline_id)
+        variables = fetch_sched_variables(sched_pipeline)
+
+    try:
+        with open(args.var_file, "w") as file:
+            json.dump(variables, file, indent=2)
+    except FileNotFoundError:
+        print(f"Error: Writing File to '{args.var_file}'")
+        exit(1)
+
 
 # Run the main function
 if __name__ == "__main__":
     main()
-
diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index 5e51a25..1ed43c2 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -1,91 +1,115 @@
-import requests
-import json
 import argparse
+import json
+
+import gitlab
 
-# Load configuration from JSON file
-def load_config(file_path):
+
+def load_file(file_path):
     try:
-        with open(file_path, 'r') as file:
+        with open(file_path, "r") as file:
             return json.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(config, var_name, var_value):
-    gitlab_url = config["gitlab_url"]
-    project_id = config["dest_project_id"]
-    private_token = config["private_token"]
-
-    headers = {
-        "PRIVATE-TOKEN": private_token,
-        "Content-Type": "application/json"
-    }
-
-    url = f"{gitlab_url}/api/v4/projects/{project_id}/variables/{var_name}"
-
-    # Check if the variable already exists
-    response = requests.get(url, headers=headers)
-
-    if response.status_code == 404:
-        print(f"Variable '{var_name}' does not exist. Creating new variable...")
-        create_url = f"{gitlab_url}/api/v4/projects/{project_id}/variables"
-        data = {
-            "key": var_name,
-            "value": var_value,
-            "variable_type": "env_var",  # Can be 'file' for file variables
-            "protected": False,  # Set to True if you want the variable protected
-            "masked": False  # Set to True if you want the variable masked
-        }
-        create_response = requests.post(create_url, headers=headers, json=data)
-
-        if create_response.status_code == 201:
-            print(f"Variable '{var_name}' created successfully.")
+def create_or_update_variable(project, var_dict):
+    # Check if the variable exists in the project
+    p_variable = project.variables.get(var_dict["key"]).asdict()
+    if p_variable:
+        if p_variable != var_dict:
+            # Check if the attributes are the same
+            for k, v in var_dict.items():
+                if p_variable[k] != v:
+                    # If not update the value in the project
+                    print(f"Updating key {k} value")
+                    project.variables.update(k, {"value": v})
         else:
-            print(f"Failed to create variable '{var_name}'. Status code: {create_response.status_code}")
-    elif response.status_code == 200:
-        print(f"Variable '{var_name}' already exists. Updating variable...")
-        data = {"value": var_value}
-        update_response = requests.put(url, headers=headers, json=data)
-
-        if update_response.status_code == 200:
-            print(f"Variable '{var_name}' updated successfully.")
+            print(f"variable {var_dict["key"]} already exists")
+    # Create variable if it doesn't exist in the project
+    else:
+        for k, v in var_dict.items():
+            print(f"Creating variable {var_dict["key"]}")
+            project.variables.create(var_dict)
+
+
+def get_pipeline_vars_by_key(sched_pipeline, key_name):
+    p_vars = sched_pipeline.attributes["variables"]
+    # p_vars.sort(key=lambda x: x["key"])
+    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:
+        if p_variable != var_dict:
+            # Check if the attributes are the same
+            for k, v in var_dict.items():
+                if p_variable[k] != v:
+                    # If not update the value in the project
+                    print(f"Updating key {k} value")
+                    sched_pipeline.variables.delete(p_variable["key"])
+                    sched_pipeline.variables.create(var_dict)
         else:
-            print(f"Failed to update variable '{var_name}'. Status code: {update_response.status_code}")
+            print(f"variable {var_dict["key"]} already exists")
+    # Create variable if it doesn't exist in the project
     else:
-        print(f"Error checking variable '{var_name}'. Status code: {response.status_code}")
+        for k, v in var_dict.items():
+            print(f"Creating variable {var_dict["key"]}")
+            return sched_pipeline.variables.create(var_dict)
+
 
-# Main function to load the auth config and apply variables
 def main():
     # Setup argument parser
     parser = argparse.ArgumentParser(description="GitLab CI/CD Variables Updater")
     parser.add_argument(
-        "--auth_config",
+        "--config_file",
         type=str,
-        default="auth-config.json",
-        help="Path to the configuration file (default: config.json)"
+        default="gitlab.ini",
+        required=True,
+        help="Path to the configuration file (default: gitlab.ini)",
     )
     parser.add_argument(
-        "--vars",
+        "--var_file",
         type=str,
         default="ci-variables.json",
-        help="Path to the configuration file (default: config.json)"
+        help="Path to the CI vars file (default: ci-variables.json)",
+    )
+    parser.add_argument(
+        "--project_id",
+        type=int,
+        required=True,
+        help="Gitlab project ID for target variables",
+    )
+    parser.add_argument(
+        "--sched_pipeline_id",
+        type=int,
+        help="Gitlab project scheduled pipeline ID",
     )
 
     # Parse the arguments
     args = parser.parse_args()
 
-    # Load the configuration file
-    config = load_config(args.auth_config)
-    vars_dict = load_config(args.vars)
+    gl = gitlab.Gitlab.from_config("uabrc", [args.config_file])
+    project = gl.projects.get(args.project_id)
+
+    # Load the CI vars file
+    var_list = load_file(args.var_file)
+    # var_list.sort(key=lambda x: x["key"])
 
-    ci_variables = vars_dict["ci_variables"]
+    # 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)
 
-    # Loop through and create/update all variables
-    for var_name, var_value in ci_variables.items():
-        create_or_update_variable(config, var_name, var_value)
 
-# Run the main function
 if __name__ == "__main__":
     main()
-
-- 
GitLab


From 1f33598ad07f895cfbd3b03cbc42e4ec557f9b12 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Wed, 30 Apr 2025 17:21:24 -0400
Subject: [PATCH 05/18] Add an example config file to access gitlab api

---
 scripts/gitlab.ini.example | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 scripts/gitlab.ini.example

diff --git a/scripts/gitlab.ini.example b/scripts/gitlab.ini.example
new file mode 100644
index 0000000..6b21153
--- /dev/null
+++ b/scripts/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
-- 
GitLab


From 05fc1163c1dff5ca7cb9d912bf9ffae411fb398c Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Thu, 1 May 2025 14:18:36 -0400
Subject: [PATCH 06/18] Add requirements file for pip env installs

---
 requirements.txt | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 requirements.txt

diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..5149467
--- /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
-- 
GitLab


From ef30db8fcc74b88f9ddad11e166c5958d113ac78 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Thu, 1 May 2025 14:47:21 -0400
Subject: [PATCH 07/18] Add yaml support for ci-variables files

The yaml format is for user convenience. It is converted into JSON
for performance. Processing is still done in JSON because it is
faster data exchange format over http.
---
 scripts/gitlab-ci-vars-reader.py  | 10 +++++-----
 scripts/gitlab-ci-vars-updater.py | 10 +++++-----
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/scripts/gitlab-ci-vars-reader.py b/scripts/gitlab-ci-vars-reader.py
index 9e17ae9..0e8e4f9 100755
--- a/scripts/gitlab-ci-vars-reader.py
+++ b/scripts/gitlab-ci-vars-reader.py
@@ -1,7 +1,7 @@
 import argparse
-import json
 
 import gitlab
+import yaml
 
 
 # Function to fetch all CI/CD variables from a GitLab project
@@ -31,8 +31,8 @@ def main():
     parser.add_argument(
         "--var_file",
         type=str,
-        default="ci-variables.json",
-        help="Path to the CI vars file (default: ci-variables.json)",
+        default="ci-variables.yaml",
+        help="Path to the CI vars file (default: ci-variables.yaml)",
     )
     parser.add_argument(
         "--project_id",
@@ -61,8 +61,8 @@ def main():
         variables = fetch_sched_variables(sched_pipeline)
 
     try:
-        with open(args.var_file, "w") as file:
-            json.dump(variables, file, indent=2)
+        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)
diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index 1ed43c2..1221d83 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -1,13 +1,13 @@
 import argparse
-import json
 
 import gitlab
+import yaml
 
 
 def load_file(file_path):
     try:
-        with open(file_path, "r") as file:
-            return json.load(file)
+        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)
@@ -77,8 +77,8 @@ def main():
     parser.add_argument(
         "--var_file",
         type=str,
-        default="ci-variables.json",
-        help="Path to the CI vars file (default: ci-variables.json)",
+        default="ci-variables.yaml",
+        help="Path to the CI vars file (default: ci-variables.yaml)",
     )
     parser.add_argument(
         "--project_id",
-- 
GitLab


From 3e01c15af6ea89ef0eec8caebdc6c67de0fd2eac Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Thu, 1 May 2025 14:58:27 -0400
Subject: [PATCH 08/18] Use project name instead of ID for input

---
 scripts/gitlab-ci-vars-reader.py  | 9 ++++-----
 scripts/gitlab-ci-vars-updater.py | 8 ++++----
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/scripts/gitlab-ci-vars-reader.py b/scripts/gitlab-ci-vars-reader.py
index 0e8e4f9..c573a56 100755
--- a/scripts/gitlab-ci-vars-reader.py
+++ b/scripts/gitlab-ci-vars-reader.py
@@ -35,11 +35,10 @@ def main():
         help="Path to the CI vars file (default: ci-variables.yaml)",
     )
     parser.add_argument(
-        "--project_id",
-        type=int,
-        default="",
+        "--project_name",
+        type=str,
         required=True,
-        help="Gitlab project ID to read variables from",
+        help="Gitlab project name with namespace",
     )
     parser.add_argument(
         "--sched_pipeline_id",
@@ -51,7 +50,7 @@ def main():
     args = parser.parse_args()
 
     gl = gitlab.Gitlab.from_config("uabrc", [args.config_file])
-    project = gl.projects.get(args.project_id)
+    project = gl.projects.get(args.project_name)
 
     # Fetch project or sched pipeline variables
     if not args.sched_pipeline_id:
diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index 1221d83..df46972 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -81,10 +81,10 @@ def main():
         help="Path to the CI vars file (default: ci-variables.yaml)",
     )
     parser.add_argument(
-        "--project_id",
-        type=int,
+        "--project_name",
+        type=str,
         required=True,
-        help="Gitlab project ID for target variables",
+        help="Gitlab project name with namespace",
     )
     parser.add_argument(
         "--sched_pipeline_id",
@@ -96,7 +96,7 @@ def main():
     args = parser.parse_args()
 
     gl = gitlab.Gitlab.from_config("uabrc", [args.config_file])
-    project = gl.projects.get(args.project_id)
+    project = gl.projects.get(args.project_name)
 
     # Load the CI vars file
     var_list = load_file(args.var_file)
-- 
GitLab


From 0a8efb1f978b4db1eaeb1e7fff4f9d8f0d7fd5d8 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Thu, 1 May 2025 16:19:12 -0400
Subject: [PATCH 09/18] Handle the exception when variable is not found

---
 scripts/gitlab-ci-vars-updater.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index df46972..c09e68a 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -16,7 +16,11 @@ def load_file(file_path):
 # Function to create or update a GitLab CI/CD variable
 def create_or_update_variable(project, var_dict):
     # Check if the variable exists in the project
-    p_variable = project.variables.get(var_dict["key"]).asdict()
+    try:
+        p_variable = project.variables.get(var_dict["key"]).asdict()
+    except gitlab.exceptions.GitlabGetError:
+        print("Variable not found")
+        p_variable = False
     if p_variable:
         if p_variable != var_dict:
             # Check if the attributes are the same
-- 
GitLab


From e5e2c2b78c47fcf64e567244b4f2d6a8a2362700 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Thu, 1 May 2025 16:25:52 -0400
Subject: [PATCH 10/18] Add missing func return after creating var

---
 scripts/gitlab-ci-vars-updater.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index c09e68a..e80d1f7 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -35,7 +35,7 @@ def create_or_update_variable(project, var_dict):
     else:
         for k, v in var_dict.items():
             print(f"Creating variable {var_dict["key"]}")
-            project.variables.create(var_dict)
+            return project.variables.create(var_dict)
 
 
 def get_pipeline_vars_by_key(sched_pipeline, key_name):
-- 
GitLab


From f6b5cf3e6a2e3dfdd37e67994010aac4799d3958 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Fri, 2 May 2025 17:16:31 -0400
Subject: [PATCH 11/18] Add support for same key with different scopes

API doesn't support filtering the key based on env scope so we
do it manually.
---
 scripts/gitlab-ci-vars-updater.py | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index e80d1f7..b67c63e 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -15,12 +15,19 @@ def load_file(file_path):
 
 # 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
     # Check if the variable exists in the project
     try:
-        p_variable = project.variables.get(var_dict["key"]).asdict()
+        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.asdict()
+                break
     except gitlab.exceptions.GitlabGetError:
         print("Variable not found")
-        p_variable = False
+        exit(1)
     if p_variable:
         if p_variable != var_dict:
             # Check if the attributes are the same
@@ -28,7 +35,20 @@ def create_or_update_variable(project, var_dict):
                 if p_variable[k] != v:
                     # If not update the value in the project
                     print(f"Updating key {k} value")
-                    project.variables.update(k, {"value": v})
+                    # project.variables.update(k, {"value": v})
+                    project.variables.update(
+                        key,
+                        {
+                            "value": var_dict["value"],
+                            "environment_scope": scope,
+                            "variable_type": var_dict.get("variable_type", "env_var"),
+                            "masked": var_dict.get("masked", False),
+                            "protected": var_dict.get("protected", False),
+                            "raw": var_dict.get("raw", False),
+                            "description": var_dict.get("description", None),
+                        },
+                    )
+                    break
         else:
             print(f"variable {var_dict["key"]} already exists")
     # Create variable if it doesn't exist in the project
-- 
GitLab


From 4ed47f073a0451641616a612c0237acfbfd7d887 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Fri, 2 May 2025 17:31:48 -0400
Subject: [PATCH 12/18] Misc updates to commented sections

---
 scripts/gitlab-ci-vars-updater.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index b67c63e..922369a 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -18,7 +18,7 @@ def create_or_update_variable(project, var_dict):
     key = var_dict.get("key")
     scope = var_dict.get("environment_scope", "*")
     p_variable = None
-    # Check if the variable exists in the project
+    # Fetch a variable with matching key and scope
     try:
         all_vars = project.variables.list(get_all=True)
         for var in all_vars:
@@ -28,6 +28,7 @@ def create_or_update_variable(project, var_dict):
     except gitlab.exceptions.GitlabGetError:
         print("Variable not found")
         exit(1)
+    # Check if the variable exists in the project
     if p_variable:
         if p_variable != var_dict:
             # Check if the attributes are the same
@@ -35,7 +36,6 @@ def create_or_update_variable(project, var_dict):
                 if p_variable[k] != v:
                     # If not update the value in the project
                     print(f"Updating key {k} value")
-                    # project.variables.update(k, {"value": v})
                     project.variables.update(
                         key,
                         {
@@ -60,7 +60,6 @@ def create_or_update_variable(project, var_dict):
 
 def get_pipeline_vars_by_key(sched_pipeline, key_name):
     p_vars = sched_pipeline.attributes["variables"]
-    # p_vars.sort(key=lambda x: x["key"])
     for p_variable in p_vars:
         if p_variable.get("key") == key_name:
             return p_variable
@@ -124,7 +123,6 @@ def main():
 
     # Load the CI vars file
     var_list = load_file(args.var_file)
-    # var_list.sort(key=lambda x: x["key"])
 
     # Create or update all variables
     for var_dict in var_list:
-- 
GitLab


From 46928010227ff0fb0dfd849ea5f547f3d0b742f1 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Sat, 3 May 2025 03:31:07 -0400
Subject: [PATCH 13/18] Remove unnecessary loop as we update only once

---
 scripts/gitlab-ci-vars-updater.py | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index 922369a..7318de6 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -23,19 +23,11 @@ def create_or_update_variable(project, var_dict):
         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.asdict()
+                p_variable = var
                 break
     except gitlab.exceptions.GitlabGetError:
         print("Variable not found")
         exit(1)
-    # Check if the variable exists in the project
-    if p_variable:
-        if p_variable != var_dict:
-            # Check if the attributes are the same
-            for k, v in var_dict.items():
-                if p_variable[k] != v:
-                    # If not update the value in the project
-                    print(f"Updating key {k} value")
                     project.variables.update(
                         key,
                         {
@@ -49,13 +41,18 @@ def create_or_update_variable(project, var_dict):
                         },
                     )
                     break
+
+    # 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']}")
         else:
             print(f"variable {var_dict["key"]} already exists")
     # Create variable if it doesn't exist in the project
     else:
-        for k, v in var_dict.items():
-            print(f"Creating variable {var_dict["key"]}")
-            return project.variables.create(var_dict)
+        print(f"Creating variable {var_dict["key"]}")
+        return project.variables.create(var_dict)
 
 
 def get_pipeline_vars_by_key(sched_pipeline, key_name):
-- 
GitLab


From 7fc47b62846c02112b9737c050641b152a45b660 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Sat, 3 May 2025 03:40:30 -0400
Subject: [PATCH 14/18] Use delete + create rather than update function

Update doesn't support scoped variables. So we manually filter scoped
variable and use delete and create to update the scoped variable.
This will avoid the http 409 error due to conflict.
---
 scripts/gitlab-ci-vars-updater.py | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index 7318de6..458e4b8 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -28,25 +28,14 @@ def create_or_update_variable(project, var_dict):
     except gitlab.exceptions.GitlabGetError:
         print("Variable not found")
         exit(1)
-                    project.variables.update(
-                        key,
-                        {
-                            "value": var_dict["value"],
-                            "environment_scope": scope,
-                            "variable_type": var_dict.get("variable_type", "env_var"),
-                            "masked": var_dict.get("masked", False),
-                            "protected": var_dict.get("protected", False),
-                            "raw": var_dict.get("raw", False),
-                            "description": var_dict.get("description", None),
-                        },
-                    )
-                    break
 
     # 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
-- 
GitLab


From 2579d8f6a69b3236e847eff40c7cf6b02c6f8233 Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Sat, 3 May 2025 03:47:44 -0400
Subject: [PATCH 15/18] Add default fields if the user provides only key and
 value

This provides flexibility for the user to define just the value for a
key. More fields can be added for a key which will override the defaults
---
 scripts/gitlab-ci-vars-updater.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index 458e4b8..e66b88f 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -18,6 +18,20 @@ 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)
-- 
GitLab


From 9c457bfbcc708aac432ecc88c2c9b0c6b1efbafc Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Sat, 3 May 2025 04:02:08 -0400
Subject: [PATCH 16/18] Remove unnecessary loop as we update only once

---
 scripts/gitlab-ci-vars-updater.py | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/scripts/gitlab-ci-vars-updater.py b/scripts/gitlab-ci-vars-updater.py
index e66b88f..5da49d5 100644
--- a/scripts/gitlab-ci-vars-updater.py
+++ b/scripts/gitlab-ci-vars-updater.py
@@ -70,21 +70,17 @@ 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:
-            # Check if the attributes are the same
-            for k, v in var_dict.items():
-                if p_variable[k] != v:
-                    # If not update the value in the project
-                    print(f"Updating key {k} value")
-                    sched_pipeline.variables.delete(p_variable["key"])
-                    sched_pipeline.variables.create(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:
-        for k, v in var_dict.items():
-            print(f"Creating variable {var_dict["key"]}")
-            return sched_pipeline.variables.create(var_dict)
+        print(f"Creating variable {var_dict["key"]}")
+        return sched_pipeline.variables.create(var_dict)
 
 
 def main():
-- 
GitLab


From ad4ad4b170ae6541ddfe43a4bf3c159f51efb8db Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Tue, 6 May 2025 10:31:36 -0400
Subject: [PATCH 17/18] Add Readme file for utility scripts

---
 scripts/README.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 scripts/README.md

diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 0000000..c1ad5e3
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1,20 @@
+**Prerequisites**
+python -m venv ~/venvs/gitlab
+source ~/venvs/gitlab/bin/activate
+pip install -r requirements
+
+**Clone the repo**
+git clone -b feat-gl-cicd-var-scripts --single-branch https://gitlab.rc.uab.edu/atlurie/hpc-factory.git tmp-hpc-factory
+cd tmp-hpc-factory/scripts
+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.json
+
+python3 gitlab-ci-vars-updater.py --config_file gitlab.ini --project_id <PROJECT_ID> --sched_pipeline_id <NEW-PIPELINE_ID> --var_file ci-variables.json
+```
-- 
GitLab


From 0d34d32784e752ffba5ce8ae059234264ee329ba Mon Sep 17 00:00:00 2001
From: Eesaan Atluri <atlurie@uab.edu>
Date: Tue, 6 May 2025 13:25:30 -0400
Subject: [PATCH 18/18] Rename scripts dir to utils

---
 {scripts => utils}/README.md                 | 23 +++++++++++++-------
 {scripts => utils}/gitlab-ci-vars-reader.py  |  0
 {scripts => utils}/gitlab-ci-vars-updater.py |  0
 {scripts => utils}/gitlab.ini.example        |  0
 4 files changed, 15 insertions(+), 8 deletions(-)
 rename {scripts => utils}/README.md (54%)
 rename {scripts => utils}/gitlab-ci-vars-reader.py (100%)
 rename {scripts => utils}/gitlab-ci-vars-updater.py (100%)
 rename {scripts => utils}/gitlab.ini.example (100%)

diff --git a/scripts/README.md b/utils/README.md
similarity index 54%
rename from scripts/README.md
rename to utils/README.md
index c1ad5e3..6a669e0 100644
--- a/scripts/README.md
+++ b/utils/README.md
@@ -1,20 +1,27 @@
-**Prerequisites**
+### 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
+```
 
-**Clone the repo**
-git clone -b feat-gl-cicd-var-scripts --single-branch https://gitlab.rc.uab.edu/atlurie/hpc-factory.git tmp-hpc-factory
-cd tmp-hpc-factory/scripts
+### 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**
+### 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.json
+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.json
+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/scripts/gitlab-ci-vars-reader.py b/utils/gitlab-ci-vars-reader.py
similarity index 100%
rename from scripts/gitlab-ci-vars-reader.py
rename to utils/gitlab-ci-vars-reader.py
diff --git a/scripts/gitlab-ci-vars-updater.py b/utils/gitlab-ci-vars-updater.py
similarity index 100%
rename from scripts/gitlab-ci-vars-updater.py
rename to utils/gitlab-ci-vars-updater.py
diff --git a/scripts/gitlab.ini.example b/utils/gitlab.ini.example
similarity index 100%
rename from scripts/gitlab.ini.example
rename to utils/gitlab.ini.example
-- 
GitLab