Golangで、自動生成ファイルがある場合のパッケージ自動更新
Resilire Tech Blog は zenn に移行しました
私たちのテックブログをご覧いただきありがとうございます! 2024年から掲載先を zenn に移行しました。是非フォローして最新記事をご覧ください。
これからも Resilire Tech Blog をよろしくお願いします。
はじめに
こんにちは、バックエンドエンジニアのmynkitです。
Resilireでは、バックエンドにGolangを採用しています。Golangでパッケージを自動更新させたい場合、dependabotやRenovateが選択肢になるかと思います。
ただgRPCなどの自動生成ファイルがあるときはいろいろと厄介で、結論からいうとパッケージをimportするだけのgoファイルを作る必要があります。
今回は自動生成ファイルがある場合に試行錯誤したことと、最終的に作成したdependabot.goの生成方法を共有します。
dependabotと、自動生成ファイルがあるときの課題
dependabotはパッケージアップデートを自動で行ってくれるGithub純正のBotです。
Dependabot は、セキュリティアップデートプログラムを使用してプルリクエストを発行することにより、脆弱性のある依存関係を修正できます。
とてもありがたいのですが、一つ困ったことがあってdependabotが走ってプルリクが出された際、go mod tidy
が自動で走ります。これによってgo.modに必要なパッケージの追加と不要なパッケージの削除をしてくれるのですが、自動生成ファイルがあるときには悪さをすることがあります。
具体的にいうと、自動生成ファイルのみが呼び出しているパッケージがgo.modから削除されます。そのため自動生成ファイル部分のコードが動かなくなり、Lintなどが落ちます。
対応方法
Renovateの検討(うまくいかなかった)
ではgo mod tidy
を自動で走らせなければいいのでは?という気持ちになります。
Renovateでは、dependabotよりもカスタム性のある自動パッケージアップデートができます。また指定しない限りはgo mod tidy
が走らないので今回のケースでも良い選択肢に見えます。
そこで実際に試してみたのですが、自動テスト実行時に
go: updates to go.mod needed; to update it: go mod tidy
と怒られてしまいました。。
Renovateではrenovate.jsonに
"postUpdateOptions": [ "gomodTidy" ],
を追記することでgo mod tidy
を実行させることが可能ですが、Renovateのgo mod tidy
はgo.sumからパッケージのsumが抜け落ちることがあるようです。(そしてこの問題は対応しない方針のようです)
https://github.com/renovatebot/renovate/issues/3017
そのため、今回の場合Renovateを使う選択肢はなさそうです。
dependabot.goの追加(うまくいった)
そもそも今回の問題が報告されていないのか調べてみると、dependabotにissueがありました。
https://github.com/dependabot/dependabot-core/issues/3300
この手法は、自動生成ファイルがつくられるディレクトリにdependabot.goを用意し、自動生成ファイルに用いるパッケージをすべてimportしておくというものです。
dependabot.goの生成
すべての生成ファイルを目で確認していくのは大変なので、(なぜかPythonでつくりましたが)dependabot.go生成コードを置いておきます。
Install
import部分を認識させるため、ASTを利用します。ASTのjsonをdumpしてくれるgoastというパッケージがあるので、インストールしておきます。
go install github.com/m-mizutani/goast/cmd/goast@latest
次にPython3をインストールしておきます。必要な外部パッケージは特にありません。
generate_dependabotgo.pyの作成
# generate_dependabotgo.py import subprocess import json import glob import os def get_package_from_dic(dic): if 'Kind' in dic.keys() and dic['Kind']=='ImportSpec': return dic['Node']['Path']['Value'] else: return None def get_imported_packages(go_file): cmd = f"goast dump '{go_file}'" ast_result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True).stdout dics = [json.loads(l) for l in ast_result.replace('\n}\n{\n', '}\t{').replace('\n', '').replace('\t', '\n').splitlines()] return set([get_package_from_dic(d) for d in dics if get_package_from_dic(d)]) def gen_dependabotgofile(dir_path): all_packages = [] if dir_path[-1] == '/': dir_path = dir_path[:-1] basename = os.path.basename(dir_path) go_paths = glob.glob(f'{dir_path}/*.go', recursive=True) if len(go_paths) == 0: Exception(f'dir_path: {dir_path} が適切ではありません') for go_path in go_paths: all_packages.extend(get_imported_packages(go_path)) import_txt = '' import_txt += f'package {basename}\n' import_txt += '\n' import_txt += 'import (\n' for package in sorted(set(all_packages)): import_txt += f'\t_ {package}\n' import_txt += ')\n' print(import_txt) with open(f'{dir_path}/dependabot.go', mode='w') as f: f.write(import_txt) def goformat(dir_path): subprocess.run(f'goimports -w "{dir_path}/dependabot.go"', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) auto_generated_gofile_dirs = [ # 自動生成ファイルの作られるディレクトリ達 ] for dir_path in auto_generated_gofile_dirs: print(dir_path) gen_dependabotgofile(dir_path) goformat(dir_path)
生成手順
ローカルで実行
- gRPCなど自動生成ファイルを生成
- generate_dependabotgo.pyの
auto_generated_gofile_dirs
を書き換えて、python generate_dependabotgo.py
を実行
※ちなみにこの方法だとdependabot.goに標準パッケージのimportも含まれるので、気持ち悪い方は適宜削除してください。
出力されたdependabot.goはこんな感じになると思います。dependabot.goがgitの追跡対象になるように.gitignoreの設定も忘れずに。
package account import ( _ "bytes" _ "context" _ "errors" _ "fmt" _ "net" _ "net/mail" _ "net/url" _ "reflect" _ "regexp" _ "sort" _ "strings" _ "sync" _ "time" _ "unicode/utf8" _ "google.golang.org/grpc" _ "google.golang.org/grpc/codes" _ "google.golang.org/grpc/status" _ "google.golang.org/protobuf/reflect/protoreflect" _ "google.golang.org/protobuf/runtime/protoimpl" _ "google.golang.org/protobuf/types/known/anypb" )
dependabot.goがちゃんと機能しているか確認
最後に、dependabotによってgo mod tidy
が実行されても問題なさそうか確認しておきましょう。
- 自動生成ファイルをすべて削除
go mod tidy
を実行- 自動生成ファイルを再度生成
- TestやLintを実行してエラーがでないか確認
以上で、dependabot.go配置による自動パッケージアッブデート問題は解消します。
おわりに
Resilireでは仲間を募集しています。
サーバーサイドだけでなく、フロントエンドやSREの採用も積極的に行っています。話を聴いてみたい!だけでも良いので、ご興味ある方はぜひご連絡ください!