Skip to content

Commit cc6947f

Browse files
ci: use ssh for GitLab mirror workflow
1 parent 1da0d50 commit cc6947f

1 file changed

Lines changed: 76 additions & 38 deletions

File tree

.github/workflows/__mirror-github-to-gitlab.yml

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
permissions: {}
2020
runs-on: ubuntu-latest
2121
steps:
22-
# Keep private repository metadata out of a matrix because matrix values are visible in public workflow runs.
22+
# Process repositories in one job so project creation, privacy checks, and mirroring stay together.
2323
- name: Get repositories
2424
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
2525
with:
@@ -70,12 +70,15 @@ jobs:
7070
GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
7171
GITLAB_API_URL: https://gitlab.com/api/v4
7272
GITLAB_GROUP: lizardbyte
73+
GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
7374
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
7475
run: |
7576
set -euo pipefail
7677
77-
if [[ -z "${GITHUB_TOKEN}" ]] || [[ -z "${GITLAB_TOKEN}" ]]; then
78-
echo "::error::GH_BOT_TOKEN and GITLAB_TOKEN must both be configured."
78+
if [[ -z "${GITHUB_TOKEN}" ]] || \
79+
[[ -z "${GITLAB_TOKEN}" ]] || \
80+
[[ -z "${GITLAB_SSH_PRIVATE_KEY}" ]]; then
81+
echo "::error::GH_BOT_TOKEN, GITLAB_TOKEN, and GITLAB_SSH_PRIVATE_KEY must all be configured."
7982
exit 1
8083
fi
8184
@@ -84,6 +87,45 @@ jobs:
8487
temp_root="$(mktemp -d)"
8588
trap 'rm -f "${response_file}" "${repository_file}"; rm -rf "${temp_root}"' EXIT
8689
90+
ssh_dir="${temp_root}/ssh"
91+
ssh_key="${ssh_dir}/id_ed25519"
92+
ssh_known_hosts="${ssh_dir}/known_hosts"
93+
mkdir -m 700 "${ssh_dir}"
94+
printf '%s\n' "${GITLAB_SSH_PRIVATE_KEY}" > "${ssh_key}"
95+
chmod 600 "${ssh_key}"
96+
if ! ssh-keygen -y -P '' -f "${ssh_key}" > /dev/null; then
97+
echo "::error::GITLAB_SSH_PRIVATE_KEY is not a valid unencrypted SSH private key."
98+
exit 1
99+
fi
100+
101+
# Published at https://docs.gitlab.com/user/gitlab_com/#ssh-known_hosts-entries.
102+
gitlab_host_key='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf'
103+
printf 'gitlab.com %s\n' "${gitlab_host_key}" > "${ssh_known_hosts}"
104+
chmod 600 "${ssh_known_hosts}"
105+
106+
gitlab_host_fingerprint="$(ssh-keygen -lf "${ssh_known_hosts}" -E sha256 | awk '{print $2}')"
107+
if [[ "${gitlab_host_fingerprint}" != \
108+
'SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8' ]]; then
109+
echo "::error::The configured GitLab SSH host key has an unexpected fingerprint."
110+
exit 1
111+
fi
112+
113+
gitlab_ssh_args=(
114+
-i "${ssh_key}"
115+
-o BatchMode=yes
116+
-o IdentitiesOnly=yes
117+
-o StrictHostKeyChecking=yes
118+
-o "UserKnownHostsFile=${ssh_known_hosts}"
119+
-o ConnectTimeout=30
120+
)
121+
printf -v gitlab_ssh_command '%q ' ssh "${gitlab_ssh_args[@]}"
122+
123+
echo "Verifying GitLab SSH authentication..."
124+
if ! ssh "${gitlab_ssh_args[@]}" -T git@gitlab.com; then
125+
echo "::error::Unable to authenticate to GitLab with GITLAB_SSH_PRIVATE_KEY."
126+
exit 1
127+
fi
128+
87129
gitlab_request() {
88130
local method="$1"
89131
local url="$2"
@@ -119,42 +161,32 @@ jobs:
119161
group_id="$(jq -er '.id' "${response_file}")"
120162
121163
github_auth="$(printf 'x-access-token:%s' "${GITHUB_TOKEN}" | base64 --wrap=0)"
122-
gitlab_auth="$(printf 'oauth2:%s' "${GITLAB_TOKEN}" | base64 --wrap=0)"
123164
echo "::add-mask::${github_auth}"
124-
echo "::add-mask::${gitlab_auth}"
125165
126166
repository_count="$(jq -er 'length' "${repository_file}")"
127167
for ((index = 0; index < repository_count; index++)); do
128168
repository="$(jq -ec ".[${index}]" "${repository_file}")"
169+
repository_number="$((index + 1))"
129170
source_name="$(jq -er '.name' <<< "${repository}")"
130171
source_clone_url="$(jq -er '.cloneUrl' <<< "${repository}")"
131172
target_name="$(jq -er '.targetName' <<< "${repository}")"
132173
target_path="$(jq -er '.targetPath' <<< "${repository}")"
133174
target_visibility="$(jq -er '.targetVisibility' <<< "${repository}")"
134175
description="Mirror of ${source_clone_url}"
135-
136-
if [[ "${target_visibility}" == "private" ]]; then
137-
echo "::add-mask::${source_name}"
138-
echo "::add-mask::${source_clone_url}"
139-
echo "::add-mask::${target_name}"
140-
echo "::add-mask::${target_path}"
141-
echo "::add-mask::${description}"
142-
fi
143-
144-
echo "Mirroring repository $((index + 1)) of ${repository_count}."
145176
project_path="${GITLAB_GROUP}/${target_path}"
146177
encoded_project_path="$(jq -rn --arg value "${project_path}" '$value | @uri')"
147-
if [[ "${target_visibility}" == "private" ]]; then
148-
echo "::add-mask::${project_path}"
149-
echo "::add-mask::${encoded_project_path}"
150-
fi
178+
179+
echo "::group::[${repository_number}/${repository_count}] ${source_name} -> ${project_path}"
180+
echo "Visibility: ${target_visibility}"
181+
echo "Checking for the GitLab project..."
151182
152183
if ! status="$(gitlab_request GET "${GITLAB_API_URL}/projects/${encoded_project_path}")"; then
153-
echo "::error::Unable to query the GitLab project for repository $((index + 1))."
184+
echo "::error title=Mirror failed: ${source_name}::Unable to query the GitLab project."
154185
exit 1
155186
fi
156187
157188
if [[ "${status}" == "404" ]]; then
189+
echo "Project does not exist; creating ${project_path}..."
158190
payload="$(
159191
jq -nc \
160192
--arg name "${target_name}" \
@@ -170,36 +202,41 @@ jobs:
170202
}'
171203
)"
172204
if ! status="$(gitlab_request POST "${GITLAB_API_URL}/projects" "${payload}")"; then
173-
echo "::error::Unable to create the GitLab project for repository $((index + 1))."
205+
echo "::error title=Mirror failed: ${source_name}::Unable to create the GitLab project."
174206
exit 1
175207
fi
176208
if [[ "${status}" != "201" ]]; then
177-
echo "::error::Unable to create the GitLab project for repository $((index + 1)) (HTTP ${status})."
209+
error_message="Unable to create the GitLab project (HTTP ${status})."
210+
echo "::error title=Mirror failed: ${source_name}::${error_message}"
178211
exit 1
179212
fi
180213
elif [[ "${status}" != "200" ]]; then
181-
echo "::error::Unable to query the GitLab project for repository $((index + 1)) (HTTP ${status})."
214+
echo "::error title=Mirror failed: ${source_name}::Unable to query the GitLab project (HTTP ${status})."
182215
exit 1
216+
else
217+
echo "Found existing GitLab project ${project_path}."
183218
fi
184219
185220
project_id="$(jq -er '.id' "${response_file}")"
186221
187222
# Make a private source private before changing metadata or pushing any Git data.
188223
if [[ "${target_visibility}" == "private" ]]; then
224+
echo "Securing ${project_path} as private before mirroring..."
189225
privacy_payload="$(jq -nc '{visibility: "private"}')"
190226
if ! status="$(
191227
gitlab_request PUT "${GITLAB_API_URL}/projects/${project_id}" "${privacy_payload}"
192228
)"; then
193-
echo "::error::Unable to secure the GitLab project for repository $((index + 1))."
229+
echo "::error title=Mirror failed: ${source_name}::Unable to secure the GitLab project."
194230
exit 1
195231
fi
196232
if [[ "${status}" != "200" ]] || \
197233
[[ "$(jq -er '.visibility' "${response_file}")" != "private" ]]; then
198-
echo "::error::GitLab privacy verification failed for repository $((index + 1))."
234+
echo "::error title=Mirror failed: ${source_name}::GitLab privacy verification failed."
199235
exit 1
200236
fi
201237
fi
202238
239+
echo "Updating and verifying the GitLab project metadata..."
203240
payload="$(
204241
jq -nc \
205242
--arg description "${description}" \
@@ -209,45 +246,46 @@ jobs:
209246
if ! status="$(
210247
gitlab_request PUT "${GITLAB_API_URL}/projects/${project_id}" "${payload}"
211248
)"; then
212-
echo "::error::Unable to update the GitLab project for repository $((index + 1))."
249+
echo "::error title=Mirror failed: ${source_name}::Unable to update the GitLab project."
213250
exit 1
214251
fi
215252
if [[ "${status}" != "200" ]]; then
216-
echo "::error::Unable to update the GitLab project for repository $((index + 1)) (HTTP ${status})."
253+
echo "::error title=Mirror failed: ${source_name}::Unable to update the GitLab project (HTTP ${status})."
217254
exit 1
218255
fi
219256
220257
actual_description="$(jq -er '.description // ""' "${response_file}")"
221258
actual_visibility="$(jq -er '.visibility' "${response_file}")"
222259
if [[ "${actual_description}" != "${description}" ]]; then
223-
echo "::error::GitLab description verification failed for repository $((index + 1))."
260+
echo "::error title=Mirror failed: ${source_name}::GitLab description verification failed."
224261
exit 1
225262
fi
226263
if [[ "${actual_visibility}" != "${target_visibility}" ]]; then
227-
echo "::error::GitLab visibility verification failed for repository $((index + 1))."
264+
echo "::error title=Mirror failed: ${source_name}::GitLab visibility verification failed."
228265
exit 1
229266
fi
230267
231-
target_clone_url="$(jq -er '.http_url_to_repo' "${response_file}")"
232-
if [[ "${target_visibility}" == "private" ]]; then
233-
echo "::add-mask::${target_clone_url}"
234-
fi
268+
target_clone_url="$(jq -er '.ssh_url_to_repo' "${response_file}")"
235269
236270
mirror_dir="${temp_root}/repository.git"
271+
echo "Cloning ${source_clone_url} as a mirror..."
237272
if ! git \
238273
-c http.https://github.com/.extraheader="AUTHORIZATION: basic ${github_auth}" \
239-
clone --mirror --quiet "${source_clone_url}" "${mirror_dir}"; then
240-
echo "::error::Unable to clone GitHub repository $((index + 1))."
274+
clone --mirror --progress "${source_clone_url}" "${mirror_dir}"; then
275+
echo "::error title=Mirror failed: ${source_name}::Unable to clone the GitHub repository."
241276
exit 1
242277
fi
278+
echo "Pushing the mirror to ${target_clone_url}..."
243279
if ! git \
244280
-C "${mirror_dir}" \
245-
-c http.https://gitlab.com/.extraheader="AUTHORIZATION: basic ${gitlab_auth}" \
246-
push --mirror --quiet "${target_clone_url}"; then
247-
echo "::error::Unable to push GitLab mirror $((index + 1))."
281+
-c core.sshCommand="${gitlab_ssh_command}" \
282+
push --mirror --progress "${target_clone_url}"; then
283+
echo "::error title=Mirror failed: ${source_name}::Unable to push the GitLab mirror."
248284
exit 1
249285
fi
250286
rm -rf "${mirror_dir}"
287+
echo "Completed mirror for ${source_name}."
288+
echo "::endgroup::"
251289
done
252290
253291
echo "Mirrored ${repository_count} repositories to GitLab."

0 commit comments

Comments
 (0)