Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

goでプルリク作ってリリース作業を改善してみた。

こんにちわ、エンジニアの山田です。

2022年始まって1ヶ月ですが、これだけWeb3がもてはやされると、自分もヤマダ2ぐらいにはならないとヤバいのではという危機感を覚えますね。

どういうことだろう(´・ω・`)ゞ

さて、今回はヤプリのサーバサイドのリリース作業についての説明と、ちょっとした改善をした話を書きたいと思います。

サーバサイドのリリース作業

現在サーバサイドのリリースは週単位で構成されています。
毎週金曜にチームでリリース内容の共有を行い、翌月曜〜火曜にステージング環境でリグレッションテストを行い水曜にリリースされます。

デプロイ作業自体はCircleCIのCI/CD環境が構築されてます。 masterブランチがステージング環境に、productionブランチが本番環境に紐付いており各環境へのデプロイ作業はブランチのマージを引き金に自動で処理されます。

また、masterやproductionブランチへのマージは1名以上のapproveが必須となっているので、うっかりマージは無いようになってます。

最近面倒なこと

サーバサイドはモノリス思考に寄ったリポジトリが構成されており基本的には少ないリポジトリに集約されてました。
が、最近は配信基盤やデータ集計基盤など各種サービス郡に沿ったリポジトリが増えてきており、結果的にリリース管理する対象も増加しました。

毎週リリース作業では、各リポジトリ毎に以下の作業が発生します。

1. master→productionへのプルリクを作成する。
2. プルリクの確認URLを別担当に共有してapproveをもらう。
3. マージする。

特別面倒な作業ではありませんが、多くのリポジトリを対象に真心込めて行うのはとても面倒です。出来る部分をサクッと改善することにしました。

改善してみた

サクッと改善ということで、簡単に自動化できそうな以下作業に絞ったスクリプト作成をゴールにします。

1. リリース対象の全リポジトリの master→production へのプルリクを作成する。
2. 確認用の各リポジトリのプルリクURLを作成する。


内部で利用する目的なので「頑張らない」を信条にヤプリの主言語たるGoで簡単なスクリプトを作ります。
今回使うパッケージはこちら。

github.com/google/go-github/v42/github


1.まずはgithub tokenを用いてgithub client作成する。(github tokenは標準入力より取得したものを利用)

func getGithubClient(ctx context.Context, githubToken string) *github.Client {
    ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken})
    tc := oauth2.NewClient(ctx, ts)
    return github.NewClient(tc)
}


2.github clientを用いて事前にチェック対象のリポジトリが存在する事を確認する。

// githubより、リポジトリ一覧を取得
func getRepositoriesByGithub(ctx context.Context, client *github.Client) ([]string, error) {
    // todo. 現状PerPage100の一回で足りるので手抜き実装
    opt := &github.RepositoryListByOrgOptions{Type: "all", ListOptions: github.ListOptions{Page: 1, PerPage: 100}}
    repos, _, err := client.Repositories.ListByOrg(ctx, GithubOrgName, opt)
    if err != nil {
        return nil, err
    }
    var repositories []string
    for _, repo := range repos {
        repositories = append(repositories, *repo.Name)
    }
    return repositories, nil
}
// 対象のリポジトリがgithubに存在する事を確認
func checkRepositoriesExists(ctx context.Context, client *github.Client, repositories []string) error {
    repositoriesByGithub, err := getRepositoriesByGithub(ctx, client)
    if err != nil {
        return err
    }
    var notExistRepositories []string
    for _, repo := range repositories {
        isExist := false
        for i := range repositoriesByGithub {
            if repo == repositoriesByGithub[i] {
                isExist = true
                break
            }
        }
        if !isExist {
            notExistRepositories = append(notExistRepositories, repo)
        }
    }
    if len(notExistRepositories) > 0 {
        return errors.Errorf("list of no exists repositories:%s", notExistRepositories)
    }
    return nil
}

3.各リポジトリ郡の master->production プルリクを作成する。

func makePRsMasterToProduction(ctx context.Context, client *github.Client, repos []string) error {
    base := "production"
    head := "master"
    title := fmt.Sprintf("reguler-%s", time.Now().Format("2006-01-02"))

    fmt.Println("\n====================")
    for _, repo := range repos {
        pr := client.PullRequests
        prBody, _, err := pr.Create(
            ctx,
            GithubOrgName,
            repo,
            &github.NewPullRequest{Title: github.String(title), Head: github.String(head), Base: github.String(base)},
        )
        if err != nil {
            if strings.Contains(err.Error(), "pull request already exists") {
                // Resource:PullRequest Field: Code:custom Message:A pull request already exists for ***.
                fmt.Println("pull request already exists:", repo)
                continue
            } else if strings.Contains(err.Error(), "No commits between") {
                // Resource:PullRequest Field: Code:custom Message:No commits between *** and ***.
                fmt.Println("no commits: https://github.com/" + GithubOrgName + "/" + repo + "/compare/" + base + "..." + head)
                continue
            }
            return err
        }
        fmt.Println(prBody.GetHTMLURL())
    }
    fmt.Println("====================")

    return nil
}

差分がないものはno commits、すでに存在する場合はpull request already exists と出力されるようにしました。

出力結果はこんな感じです。

> go run ./main/cli/github/make-release-pr
please input the github token
************************************* (github tokenを入力)

====================
https://github.com/<組織名>/<リポジトリ1>/pull/1111
https://github.com/<組織名>/<リポジトリ2>/pull/2222
https://github.com/<組織名>/<リポジトリ3>/pull/3333
no commits: https://github.com/<組織名>/<リポジトリ4>/compare/production...master
no commits: https://github.com/<組織名>/<リポジトリ5>/compare/production...master
no commits: https://github.com/<組織名>/<リポジトリ6>/compare/production...master
pull request already exists: <リポジトリ7>
====================

意図通りの出力結果が得られたので、これで一個一個真心を込めてプルリクを作成する必要がなくなりました!

まとめ

以上、ちょっとした日常のルーチン作業を楽にする改善を行ってみました。ヤプリでは隔週でYappdate Dayという日を設けていて、こういった改善業務を積極的に行っています。

ちょっとした改善で日常の工数削減や効率化は積もり積もって効果が出るので、地道な改善はとても大事ですね。( ゚∀゚) やっぷりヤプリ!