From 2292a2a2037c59ed7dcdbb12b8fe12193d41fbe8 Mon Sep 17 00:00:00 2001 From: Ryan Krattiger Date: Mon, 6 Jul 2026 16:39:16 -0500 Subject: [PATCH 1/3] gh-gl-sync: Add option for using a gitlab oauth token Passing private base64 encoded ssh keys doesn't make sense in all context. When running the sync script as a cron from Gitlab itself using the temporary job token is better practice. --- images/gh-gl-sync/SpackCIBridge.py | 61 +++++++++++++++++++++++-- images/gh-gl-sync/test_SpackCIBridge.py | 16 +++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/images/gh-gl-sync/SpackCIBridge.py b/images/gh-gl-sync/SpackCIBridge.py index b3c1b98c2..a96e0d7cf 100644 --- a/images/gh-gl-sync/SpackCIBridge.py +++ b/images/gh-gl-sync/SpackCIBridge.py @@ -41,6 +41,15 @@ def _durable_subprocess_run(*args, **kwargs): time.sleep(2 ** (1 + attempt_num)) +def _write_secret(file: str, content: str): + # Create the file with the oatuh token + with open(file, "wb") as fd: + fd.write(b"") + os.chmod(file, 0o600) + with open(file, "wb") as fd: + fd.write(content.encode()) + + class SpackCIBridge(object): def __init__(self, gitlab_repo="", gitlab_host="", gitlab_project="", github_project="", @@ -101,8 +110,44 @@ def cleanup(): print(" Shutting down ssh-agent({0})".format(os.environ["SSH_AGENT_PID"])) _durable_subprocess_run(["ssh-agent", "-k"]) + def setup_git_auth(self, ssh_key_base64, token): + """Configure authentication for pushing branches to gitlab + + Args: + ssh_key_base64: Base64 encoded ssh key + token: OAuth token for gitlab + """ + if ssh_key_base64: + self.setup_ssh(ssh_key_base64) + elif token: + self.setup_oauth(token) + else: + raise Exception("Failed to setup auth for pushing to git remote.") + + def setup_oauth(self, token: str): + """Configure the origin""" + + url = urllib.parse.urlparse(self.gitlab_repo) + if not url.scheme.startswith("http"): + raise Exception(f"Cannot configure oauth for {url.scheme}") + + # Inject the user name and OAuth token into the URL + if ":" in token: + url = url._replace(netloc=f"{token}@" + url.netloc) + else: + url = url._replace(netloc=f"spackbot:{token}@" + url.netloc) + + _write_secret(".git-credentials", urllib.parse.urlunparse(url) + "\n") + + # Write the credentials to the file + _durable_subprocess_run( + [ + "git", "config", "credential.helper", "store --file .git-credentials", + ], + stdout=subprocess.DEVNULL, + ) + def setup_ssh(self, ssh_key_base64): - """Start the ssh agent.""" print("Starting ssh-agent") output = _durable_subprocess_run(["ssh-agent", "-s"], stdout=subprocess.PIPE).stdout @@ -751,9 +796,15 @@ def sync(self): args = parser.parse_args() - ssh_key_base64 = os.getenv("GITLAB_SSH_KEY_BASE64") - if ssh_key_base64 is None: - raise Exception("GITLAB_SSH_KEY_BASE64 environment is not set") + gl_url = urllib.parser.urlparse(args.gitlab_repo) + + gitlab_ssh_key_base64 = os.getenv("GITLAB_SSH_KEY_BASE64") + if gl_url.scheme == "ssh" and gitlab_ssh_key_base64 is None: + raise Exception("Missing SSH key in GITLAB_SSH_KEY_BASE64 with ssh URL for gitlab") + + gitlab_token = os.getenv("GITLAB_TOKEN") + if gl_url.scheme == "https" and gitlab_token is None: + raise Exception("Missing OAuth key in GITLAB_TOKEN with https URL for gitlab") if "GITHUB_TOKEN" not in os.environ: raise Exception("GITHUB_TOKEN environment is not set") @@ -769,5 +820,5 @@ def sync(self): main_branch=args.main_branch, prereq_checks=args.prereq_check, required_label=args.required_label) - bridge.setup_ssh(ssh_key_base64) + bridge.setup_git_auth(gitlab_ssh_key_base64, gitlab_token) bridge.sync() diff --git a/images/gh-gl-sync/test_SpackCIBridge.py b/images/gh-gl-sync/test_SpackCIBridge.py index cbad22023..774ede1b6 100644 --- a/images/gh-gl-sync/test_SpackCIBridge.py +++ b/images/gh-gl-sync/test_SpackCIBridge.py @@ -270,6 +270,22 @@ def check_pid(pid): del os.environ["SSH_AGENT_PID"] +def test_oauth(): + bridge = SpackCIBridge.SpackCIBridge(gitlab_repo="https://gitlab.spack.io") + + # Credential without a user name + bridge.setup_oauth("glpat-deadbeef") + with open(".git-credentials", "r", encoding="utf-8") as fd: + cred = fd.read().strip() + assert cred == "https://spackbot:glpat-deadbeef@gitlab.spack.io" + + # Credential with a user name + bridge.setup_oauth("someuser:glpat-deadbeef") + with open(".git-credentials", "r", encoding="utf-8") as fd: + cred = fd.read().strip() + assert cred == "https://someuser:glpat-deadbeef@gitlab.spack.io" + + def test_get_pipeline_api_template(): """Test that pipeline_api_template get constructed properly.""" bridge = SpackCIBridge.SpackCIBridge(gitlab_host="https://gitlab.spack.io", gitlab_project="zack/my_test_proj") From 579d03eebb8f233922818218a8bf510d330a51c1 Mon Sep 17 00:00:00 2001 From: Ryan Krattiger Date: Wed, 8 Jul 2026 13:16:43 -0500 Subject: [PATCH 2/3] Update images --- .github/images.yml | 2 +- k8s/production/custom/gh-gl-sync/cron-jobs.yaml | 4 ++-- k8s/production/custom/kokkos-sync/cron-jobs.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/images.yml b/.github/images.yml index 84894025b..0243d33b3 100644 --- a/.github/images.yml +++ b/.github/images.yml @@ -1,6 +1,6 @@ images: - path: ./images/gh-gl-sync - image: ghcr.io/spack/ci-bridge:0.0.51 + image: ghcr.io/spack/ci-bridge:0.0.52 - path: ./images/ci-key-clear image: ghcr.io/spack/ci-key-clear:0.0.4 diff --git a/k8s/production/custom/gh-gl-sync/cron-jobs.yaml b/k8s/production/custom/gh-gl-sync/cron-jobs.yaml index c24d82317..c124ff625 100644 --- a/k8s/production/custom/gh-gl-sync/cron-jobs.yaml +++ b/k8s/production/custom/gh-gl-sync/cron-jobs.yaml @@ -16,7 +16,7 @@ spec: restartPolicy: Never containers: - name: sync - image: ghcr.io/spack/ci-bridge:0.0.51 + image: ghcr.io/spack/ci-bridge:0.0.52 imagePullPolicy: IfNotPresent resources: requests: @@ -69,7 +69,7 @@ spec: restartPolicy: Never containers: - name: sync - image: ghcr.io/spack/ci-bridge:0.0.51 + image: ghcr.io/spack/ci-bridge:0.0.52 imagePullPolicy: IfNotPresent resources: requests: diff --git a/k8s/production/custom/kokkos-sync/cron-jobs.yaml b/k8s/production/custom/kokkos-sync/cron-jobs.yaml index fb2dd5b0e..546eabc05 100644 --- a/k8s/production/custom/kokkos-sync/cron-jobs.yaml +++ b/k8s/production/custom/kokkos-sync/cron-jobs.yaml @@ -16,7 +16,7 @@ spec: restartPolicy: Never containers: - name: sync - image: ghcr.io/spack/ci-bridge:0.0.51 + image: ghcr.io/spack/ci-bridge:0.0.52 imagePullPolicy: IfNotPresent resources: requests: From 12f151c8b552228f6821a974f4de64add93b65bc Mon Sep 17 00:00:00 2001 From: Ryan Krattiger <80296582+kwryankrattiger@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:58:09 -0500 Subject: [PATCH 3/3] Update images/gh-gl-sync/SpackCIBridge.py Co-authored-by: Tim Haines --- images/gh-gl-sync/SpackCIBridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/gh-gl-sync/SpackCIBridge.py b/images/gh-gl-sync/SpackCIBridge.py index a96e0d7cf..2d3100e10 100644 --- a/images/gh-gl-sync/SpackCIBridge.py +++ b/images/gh-gl-sync/SpackCIBridge.py @@ -796,7 +796,7 @@ def sync(self): args = parser.parse_args() - gl_url = urllib.parser.urlparse(args.gitlab_repo) + gl_url = urllib.parse.urlparse(args.gitlab_repo) gitlab_ssh_key_base64 = os.getenv("GITLAB_SSH_KEY_BASE64") if gl_url.scheme == "ssh" and gitlab_ssh_key_base64 is None: