记一次为 Gerrit 迁移 GitHub 登录插件

起因

公司的 Code Review 平台(以下简称 cr )是用 Gerrit 搭建的,之前用的登录插件是 github-oauth,从我接手 cr 后,发现这个登录插件有个比较头疼的问题,就是它存储到数据库中的 externalId 是 OAuth 拿到的 access_token,如图:

access_token.png

github_oauth 后面的字符串就是 access_token,但是这个 access_token 会失效,失效的原因很多,有可能时间太久失效,改 GitHub 用户名也会造成其失效,用户也可以手动撤销。一旦 access_token 失效,用户再次登录 cr 的时候就会新建一个用户,并造成其他一些诸如邮箱无法绑定的问题。

解决方案

由于我自己之前也搭建过 Gerrit,所以很轻松的找到了解决方案,那就是更换登录插件,我所找到的登录插件就是 gerrit-oauth-provider,这个插件存储在数据库中的 externalId 是用户的 GitHub UID:

github_uid.png

当然,从 gerrit-oauth-provider 的名字也可以看出,它不仅仅支持 GitHub 登录。实际上它还支持 Google、Bitbucket、GitLab 等其他方式登录,不过我们暂时没有这些需求就是了。

迁移

麻烦的问题

迁移的过程有点麻烦,麻烦就麻烦在从 Gerrit 的 v2.15 开始,用户登录相关的信息不再存储在数据库中,而是强制存储到 Gerrit 项目自己基于 Git 实现的 NoteDb 中了。由于在我接手之前 cr 已经由其他同事升级到了 v2.15,所以用户登录相关的数据已经被存储到了 NoteDb 中,也就是说我不能通过简单的数据库操作来实现登录信息的迁移。

直接修改 NoteDb 中的数据

虽然 NoteDb 是个新东西,网上也几乎找不到什么相关资料,但它毕竟是基于 Git 的,Git可不算新了。所以经过一番研究,我总算是找到了直接修改 NoteDb 中用户登录信息的方法,可以按照下面的步骤和要点来实现:

1. 登录服务器

首先我们直接登录到部署 Gerrit 的服务器,假设 Gerrit 所在的路径为 /gerrit_site,则用户信息保存在 /gerrit_site/git/All-Users.git/ 这个 Git 仓库中。

2. 克隆 All-Users 仓库

git clone /gerrit_site/git/All-Users.git/ All-Users

3. 检出 external-ids

cd All-Users
git fetch origin refs/meta/external-ids:refs/meta/external-ids
git checkout refs/meta/external-ids

然后在工作区就能看到很多文件,每个文件里都存储着用户的 externalId,例如:

$ cat 31/7192ba2a64308271c3e5e48235d9104b69485b
[externalId "external:github_oauth:1500400095c1933ce09a4e465a82d64a1e576139"]
    accountId = 1000474

4. externalId 与文件名的对应关系

$ echo -n "external:github_oauth:1500400095c1933ce09a4e465a82d64a1e576139"|sha1sum 
317192ba2a64308271c3e5e48235d9104b69485b  -

5. 获取 GitHub UID 及邮箱

$ curl -so- https://api.github.com/user?access_token=1500400095c1933ce09a4e465a82d64a1e576139|grep -P '"id":|"email":'
  "id": 1524609,
  "email": "nanpuyue@gmail.com",

6. 存储 GitHub UID 及邮箱

$ echo -n "github-oauth:1524609"|sha1sum 
13d9f4ad1860b7b7c5b52fb146f173ac20cb801f  -
$ git config -f "13/d9f4ad1860b7b7c5b52fb146f173ac20cb801f" "externalId.github-oauth:1524609.accountId" "1000474"
$ git config -f "13/d9f4ad1860b7b7c5b52fb146f173ac20cb801f" "externalId.github-oauth:1524609.email" "nanpuyue@gmail.com"
$ cat 13/d9f4ad1860b7b7c5b52fb146f173ac20cb801f
[externalId "github-oauth:1524609"]
    accountId = 1000474
    email = nanpuyue@gmail.com

7. 提交修改

git add .
git commit -m "Update external IDs"

8. 更新引用,应用到 NoteDb

git update-ref refs/meta/external-ids $(git rev-parse HEAD)
git push origin refs/meta/external-ids

以上,就是手动修改 NodeDb 迁移用户登录信息的过程。但是成百上千的用户信息不可能一条一条的去改,所以我写了一个脚本来帮助我完成上面第 5、6 步的工作。

自动化脚本

Gist:migrate_github_plugin.sh

注意脚本依赖 jq

sudo apt install jq
#!/bin/bash
# file: migrate_github_plugin.sh
# date: 2018-06-30
# license: GPLv3 https://www.gnu.org/licenses/gpl-3.0.txt
# author: nanpuyue <nanpuyue@gmail.com> https://blog.nanpuyue.com

search_account_meta(){
    grep -r "$*" ??/|awk -F':' '{print $1}'|uniq
}

search_account_meta2(){
    grep -r "$*" ??/|awk -F':' '{print $1}'|uniq|\
    while read line;do
        echo -e "---\n$line:"
        cat $line
        echo
    done
}

migrate_github_plugin(){
    local external_id_info access_token account_id userinfo username github_uid email
    external_id_info="$(git config -lf $1)"
    access_token=$(echo "$external_id_info"|grep -Po "(?<=github_oauth\:)[0-9a-f]*(?=\.)")
    if [ -n "$access_token" ]; then
        account_id=$(echo "$external_id_info"|grep -Po "(?<=accountid\=)[0-9]*")
        echo -e "---\naccess_token: \t$access_token" >> ../migrate.log
        echo -e "account_id:\t$account_id" >> ../migrate.log
        userinfo=$(curl -s https://api.github.com/user?access_token=${access_token})
        if ( echo "$userinfo"|grep -q "Bad credentials" ); then
            echo "$account_id $access_token" >> ../bad_credentials.txt
            echo "bad credentials" >> ../migrate.log
            echo "account $account_id: bad credentials"
            rm -rvf "$1"
            echo
            return 1
        fi
        username=$(echo "$userinfo"|jq .login|tr -d '"')
        echo -e "username:\t$username" >> ../migrate.log
        github_uid=$(echo "$userinfo"|jq .id)
        echo -e "github_uid:\t$github_uid" >> ../migrate.log
        email=$(echo "$userinfo"|jq .email|tr -d '"')
        echo -e "email:\t\t$email" >> ../migrate.log
        if [[ "$github_uid" != null ]]; then
            local external_id_new="github-oauth:$github_uid"
            local file=$(echo -n "$external_id_new"|sha1sum|tr -d ' -'|sed -r 's#^(\w{2})#\1/#')
            mkdir -p $(dirname $file) || true
            git config -f "$file" "externalId.${external_id_new}.accountId" "$account_id" &&\
                echo -e "file:\t\t$file" >> ../migrate.log
            [[ "$email" != null ]] &&\
                git config -f "$file" "externalId.${external_id_new}.email" "$email"
            cat "$file"
            rm -rvf "$1"
            echo
        fi
    fi
}

上面的脚本包含三个函数,search_account_meta 搜索账户信息并输出保存该信息的文件名,search_account_meta2 还会输出完整的文件内容,migrate_github_plugin 接收包含 GitHub access_token 的文件名,自动完成登录信息迁移,并将日志记录到 ../migrate.log,同时将 access_token 失效的用户的 account_id 与 access_token 记录到 ../bad_credentials.txt

search_account_meta2 可以像这样使用:

$ cd All-Users
$ . /path/migrate_github_plugin.sh
$ search_account_meta2 nanpuyue
---
6f/fbb607356a68865fa0fc161327fc5902de6652:
[externalId "gerrit:nanpuyue"]
    accountId = 1000474

---
a7/dea9bb4fbd1b03b2b3d3f494bb43d192827d6e:
[externalId "mailto:nanpuyue@gmail.com"]
    accountId = 1000474
    email = nanpuyue@gmail.com

$ search_account_meta2 1000474
---
31/7192ba2a64308271c3e5e48235d9104b69485b:
[externalId "external:github_oauth:1500400095c1933ce09a4e465a82d64a1e576139"]
    accountId = 1000474

---
6f/fbb607356a68865fa0fc161327fc5902de6652:
[externalId "gerrit:nanpuyue"]
    accountId = 1000474

---
a7/dea9bb4fbd1b03b2b3d3f494bb43d192827d6e:
[externalId "mailto:nanpuyue@gmail.com"]
    accountId = 1000474
    email = nanpuyue@gmail.com

自动迁移:

cd All-Users
. /ptah/migrate_github_plugin.sh
search_account_meta github_oauth|haed -n50|\
while read line; do
    migrate_github_plugin "$line"
done

加入了 head -n50 表示每次只迁移 50 条用户信息,这里可以按自己的喜好来操作。

所有用户信息迁移完毕后不要忘记了前面第 7、8 步的操作。

插件本身的配置不在本文的讨论范围,可以参考插件的文档完成,值得提醒的是 gerrit.config 中 filterClass 这条配置一定要删除。

标签: Gerrit, GitHub, NoteDb

已有 7 条评论

  1. snyh snyh

    趁乔老师的access toke没过期,赶紧做坏事

    1. 早就手动撤销了,你来晚了。

  2. gaochong gaochong

    厉害,厉害;学习了

    1. 没有没有,向冲哥学习才是。

  3. leaeasy leaeasy

    里面的某同事就是我了
    乔老师辛苦了

添加新评论