Kegagalan deployment pada pipeline CI/CD sering terjadi pada tahap inisiasi kloning repositori akibat kesalahan parsing URI Git. Masalah ini umumnya dipicu oleh karakter khusus pada secret (password atau token) yang tidak di-percent-encode, serta percampuran sintaksis antara format SSH scp-like dan URL berbasis protokol standar RFC 3986.

Akar Masalah Parsing Differential: Sintaks SCP vs RFC 3986

Git mendukung dua format utama untuk pengalamatan repositori melalui protokol SSH:

  • Sintaks SCP-like: user@host:path/to/repo.git
  • Sintaks RFC 3986 (SSH URL): ssh://user@host:port/path/to/repo.git

Kegagalan parsing terjadi ketika nomor port non-standar atau sub-direktori disisipkan ke dalam sintaks scp-like, misalnya [email protected]:2222/org/repo.git. Parser Git menginterpretasikan 2222 bukan sebagai port jaringan, melainkan direktori relatif bernama 2222 milik user pada server target. Jika port non-standar digunakan, sintaks RFC 3986 wajib diterapkan secara eksplisit.

Masalah lain muncul pada protokol HTTPS saat kredensial disematkan langsung dalam authority segment (https://user:password@host/repo.git). Karakter reserved RFC 3986 seperti #, @, :, /, ?, atau % pada kata sandi memecah segmentasi parsing URL. Tanda @ tambahan pada password menyebabkan parser mengidentifikasi authority segment di posisi yang salah, memicu eror fatal: unable to access ... Could not resolve host.

Observabilitas dan Telemetri Error Log

Log bawaan CI runner sering kali menyamarkan penyebab utama di balik pesan kegagalan generik. Untuk mengisolasi error parsing, aktifkan trace telemetri internal Git dan SSH:

# Aktifkan trace detail Git dan SSH pada runner environment
export GIT_TRACE=1
export GIT_CURL_VERBOSE=1
export GIT_SSH_COMMAND="ssh -vvv -o BatchMode=yes"

git clone --bare "$RESOLVED_GIT_URI" target_dir

Daftar pola log error spesifik dan penyebab teknisnya:

  • fatal: ambiguous argument ... unknown revision: Terdapat tanda pagar (#) pada token di URI yang diinterpretasikan parser sebagai fragmen referensi atau delimiter branch.
  • ssh: Could not resolve hostname ... Name or service not known: Karakter khusus pada username atau password memicu segmentasi host yang salah pada sintaks SSH URL.
  • ssh: connect to host ... port 22: Connection refused: Menggunakan port SSH kustom pada format scp-like alih-alih skema ssh:// eksplisit.

Pre-flight Validation: Deterministic URI Parser

Pencegahan kegagalan harus dilakukan sebelum Git mengeksekusi operasi network. Gunakan tahap pre-flight deterministik untuk memvalidasi dan melakukan normalisasi URI repositori. Skrip Python berikut memverifikasi sintaks dan mengonversi format secara otomatis:

#!/usr/bin/env python3
import sys
import re
from urllib.parse import urlparse, quote

def normalize_git_uri(raw_uri: str) -> str:
    # 1. Deteksi sintaks scp-like: user@host:path/repo.git
    scp_pattern = re.compile(r"^([a-zA-Z0-9._-]+)@([a-zA-Z0-9.-]+):(.*)$")
    match = scp_pattern.match(raw_uri)
    
    if match:
        user, host, path = match.groups()
        # Jika path diawali angka dan slash, cegah misinterpretasi port
        if re.match(r"^[0-9]+/(.*)$", path):
            port, actual_path = path.split("/", 1)
            return f"ssh://{user}@{host}:{port}/{actual_path}"
        return raw_uri

    # 2. Parsing URI berbasis RFC 3986
    parsed = urlparse(raw_uri)
    if parsed.scheme in ["http", "https"]:
        if parsed.password:
            # Re-encode kata sandi untuk mencegah breaking characters
            safe_user = quote(parsed.username, safe="")
            safe_pass = quote(parsed.password, safe="")
            port_str = f":{parsed.port}" if parsed.port else ""
            return f"{parsed.scheme}://{safe_user}:{safe_pass}@{parsed.hostname}{port_str}{parsed.path}"
    
    if not parsed.scheme:
        raise ValueError(f"Format Git URI tidak dikenali: {raw_uri}")

    return raw_uri

if __name__ == "__main__":
    input_uri = sys.argv[1]
    try:
        validated_uri = normalize_git_uri(input_uri)
        print(validated_uri)
    except Exception as e:
        sys.stderr.write(f"[PRE-FLIGHT ERROR] {str(e)}\n")
        sys.exit(1)

Mekanisme Auto-Rollback saat Checkout Pipeline Gagal

Kegagalan kloning repositori di tahap deployment pipeline tidak boleh meninggalkan status environment dalam kondisi stale atau half-deployed. Gunakan penanganan sinyal kegagalan checkout untuk memicu deterministic rollback ke commit stabil terakhir:

#!/usr/bin/env bash
set -Eeuo pipefail

DEPLOY_DIR="/var/www/production-app"
BACKUP_DIR="/var/www/production-app-last-known-good"
NORMALIZED_URI="$1"
TARGET_COMMIT="$2"

cleanup_on_failure() {
    local exit_code=$?
    if [ $exit_code -ne 0 ]; then
        echo "[CRITICAL] Checkout gagal dengan exit code $exit_code. Memulai rollback deterministik..."
        if [ -d "$BACKUP_DIR" ]; then
            rm -rf "$DEPLOY_DIR"
            cp -a "$BACKUP_DIR" "$DEPLOY_DIR"
            systemctl restart app-service || true
            echo "[INFO] Rollback ke backup stabil selesai dioperasikan."
        fi
    fi
    exit $exit_code
}

trap cleanup_on_failure ERR

# Sinkronisasi state lokal
if [ -d "$DEPLOY_DIR" ]; then
    rm -rf "$BACKUP_DIR"
    cp -a "$DEPLOY_DIR" "$BACKUP_DIR"
fi

# Operasi fetch dan checkout
mkdir -p "$DEPLOY_DIR"
cd "$DEPLOY_DIR"

if [ ! -d ".git" ]; then
    git init
    git remote add origin "$NORMALIZED_URI"
else
    git remote set-url origin "$NORMALIZED_URI"
fi

git fetch --depth 1 origin "$TARGET_COMMIT"
git checkout -q "$TARGET_COMMIT"

echo "[SUCCESS] Deploy checkout berhasil pada commit $TARGET_COMMIT."

Pencegahan Kredensial Bocor pada Log Rollback

Saat menggunakan URL berbasis HTTP/HTTPS dengan token dinamis, cegah URI tercetak di log CI dengan menginstruksikan Git menggunakan credential helper memori sementara dibanding menuliskan secret langsung pada URL remote:

# Hindari menulis secret pada argumen remote
git remote set-url origin "https://github.com/org/repo.git"

# Masukkan secret via credential store in-memory (hilang saat pipeline selesai)
git config credential.helper 'cache --timeout=300'
echo -e "protocol=https\nhost=github.com\nusername=ci-runner\npassword=${SAFE_ACCESS_TOKEN}" | git credential approve

Best Practices Pengelolaan Endpoint Git pada CI/CD

  • Standarisasi Protokol: Gunakan format RFC 3986 (ssh://) secara menyeluruh jika deployment menggunakan port non-standar, atau format HTTPS murni jika terintegrasi dengan secret token provider.
  • Gunakan Deploy Key Terpisah: Hindari menyematkan kredensial password pengguna. Gunakan SSH ed25519 asymmetric keys tanpa passphrase yang dipasang pada level target repositori via CI/CD variables.
  • Sanitasi Input Input Parameter: Eksekusi parsing deterministik sebelum runner melakukan kontak TCP ke host remote untuk menekan blast radius kegagalan pipeline.