GitHub ActionsのMarketplaceを見ていたところ、気になるものがありました。
なんと「AIがコードレビューしてくれる」です。
今回はこちらについて、CI/CDを絡めた実装方法の手順説明と、実施結果の評価をやっていきたいと思います。
Cloud Formationから、やや脱線気味ですが・・・w
前提条件
構造としては、Gitでプルリクに対してChatGPTにレビューを行ってもらい、結果をレビューコメントとして、受け取ります(図の上部)これにより、メインブランチへのマージ前に、コード誤り・改善の気づきを得ることができます。
- 補足
- このActionsでは、ChatGPTのエンジンとして「ChatGPT 3.5」「4」「4 Turbo」等を指定できます。
- このレビュー機能は3.5でも使用可能ですが、上位バージョンを使用することで、レビュー精度は高くなります。
- 現在、4以降を使用するには、有償クレジットの購入(最低 5$)等が必要となります。
実装方法解説
GitHub Actionsワークフローに関する実装要素の細かいところなどは、前回の記事を参照ください。
GitHub Actionsのstepで、「anc95/ChatGPT-CodeReview@main」を使用します。
1. ChatGPTのAPI KEYを取得
高度なレビューを行いたい場合は、Credit(最低5$)を購入することで、使用可能となります。注意点を以下に挙げます。
2. GitHubレポジトリにAPI KEYを設定
- GitHubで、レビューするコードのレポジトリを開き、サインインします。
- 上部の「Settings」メニューを開き、サイドメニューから「Secrets and variables」-「Actions」を選択します。
- 「New repository secret」ボタンをクリックします。
- 「Name」に「OPENAI_API_KEY」、「Secret」に取得したAPI KEYを設定して「Add secret」ボタンをクリックします。
3. Actionsワークフローを作成
- 対象のレポジトリに対し、ルート下にワークフローファイルを追加します(ファイルの作り方は、前回のコラムを参考にしてください)
- 作成するワークフローyamlは、次のとおりです(ほぼ、公式のサンプルのままです)
name: Code Review permissions: contents: read pull-requests: write on: pull_request: types: [opened, reopened, synchronize] jobs: review: # if: ${{ contains(github.event.*.labels.*.name, 'gpt review') }} # Optional; to run only when a label is attached runs-on: ubuntu-latest steps: - uses: anc95/ChatGPT-CodeReview@main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} # Optional LANGUAGE: Japanese OPENAI_API_ENDPOINT: https://api.openai.com/v1 MODEL: gpt-3.5-turbo # https://platform.openai.com/docs/models PROMPT: # example: Please check if there are any confusions or irregularities in the following code diff: top_p: 1 # https://platform.openai.com/docs/api-reference/chat/create#chat/create-top_p temperature: 1 # https://platform.openai.com/docs/api-reference/chat/create#chat/create-temperature max_tokens: 4096 MAX_PATCH_LENGTH: 10000 # if the patch/diff length is large than MAX_PATCH_LENGTH, will be ignored and won't review. By default, with no MAX_PATCH_LENGTH set, there is also no limit for the patch/diff length.
on: pull_request
とあるとおり、このワークフローは、プルリクエストの操作時に実行されます。MODEL: gpt-3.5-turbo
の部分が、レビューエンジンの指定です。高度なAIを使用されたい方は、gpt-4-turbo
に変更してください(ただし、こちらを使用するには、前述のとおり、最低 5$のCredit購入が必要です)max_tokens: 4096
は、無償版を想定した場合の最大値です。有償版であればもっと大きい値が指定可能です。- 作成したワークフローファイルを、Master(Main)ブランチにCommit、Pushします。
4. レビュー依頼(Pull Request)を作成
- レビューして欲しいコードを、ワークブランチにPushします。
- レビューは、Pull Requestが示す変更の差分に対してだけ行われます。そのため、ソース全部をレビューさせるには、一旦ソースファイル自体をレポジトリから削除して、再度ファイルをPushしなおした方がよいです。
- GitHubのPull Requestメニューから、「New pull request」ボタンをクリックします。
- 上部でMaster(main)ブランチへのマージを指定して、「Create pull request」ボタンを押します。
- 次画面で「Create pull request」ボタンを押すと、ワークフローが開始されます。
コードレビューが終わると、プルリクへのコメントとしてレビュー結果が応答されます(メールでも配信されてきました)
以上が実施手順となります。
それではやってみましょう!
レビューのお題
レビュー対象は、前回までのCloud Formationテンプレートファイルとしました。
内容を、以下に再掲します。
▼(テンプレート全文はここをクリックしてください)
AWSTemplateFormatVersion: 2010-09-09 Parameters: # My IP Address CIDR(Required) cftMyIpAddressCidr: Type: String Description: (Required)Mapping to Inbound rule on ec2'security group. # Environment variable cftEnv: Type: String Default: Dev AllowedValues: - Prd - Stg - Dev Description: Enter Environment Prd, Stg or Dev. Default is Dev. Mappings: # Environment Mapping cftEnvMap: Prd: Suffix: prd InstanceType: t3.small VpcCidr: 10.2.0.0/21 PublicSubnetCidr: 10.2.1.0/24 PrivateSubnetCidr: 10.2.2.0/24 Stg: Suffix: stg InstanceType: t3.small VpcCidr: 10.1.0.0/21 PublicSubnetCidr: 10.1.1.0/24 PrivateSubnetCidr: 10.1.2.0/24 Dev: Suffix: dev InstanceType: t3.micro VpcCidr: 10.0.0.0/21 PublicSubnetCidr: 10.0.1.0/24 PrivateSubnetCidr: 10.0.2.0/24 Resources: # VPC cftVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !FindInMap [cftEnvMap, !Ref cftEnv, VpcCidr] EnableDnsSupport: true Tags: - Key: Name Value: !Sub - cf-tutorial-vpc-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # Internet Gateway cftIgw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub - cf-tutorial-igw-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix]} AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref cftVpc InternetGatewayId: !Ref cftIgw # Subnet (public) cftPublicSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: true VpcId: !Ref cftVpc CidrBlock: !FindInMap [cftEnvMap, !Ref cftEnv, PublicSubnetCidr] Tags: - Key: Name Value: !Sub - cf-tutorial-public-subnet-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPublicRtb: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref cftVpc Tags: - Key: Name Value: !Sub - cf-tutorial-public-rtb-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref cftPublicRtb DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref cftIgw cftPublicRtAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref cftPublicSubnet RouteTableId: !Ref cftPublicRtb # Subnet (private) cftPrivateSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false VpcId: !Ref cftVpc CidrBlock: !FindInMap [cftEnvMap, !Ref cftEnv, PrivateSubnetCidr] Tags: - Key: Name Value: !Sub - cf-tutorial-private-subnet-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPrivateRtb: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref cftVpc Tags: - Key: Name Value: !Sub - cf-tutorial-private-rtb-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref cftPrivateRtb DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref cftNatgw cftPrivateRtAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref cftPrivateSubnet RouteTableId: !Ref cftPrivateRtb # NAT Gateway cftNatgw: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt cftNatgwEip.AllocationId SubnetId: !Ref cftPublicSubnet Tags: - Key: Name Value: !Sub - cf-tutorial-natgw-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftNatgwEip: Type: AWS::EC2::EIP Properties: Domain: vpc # EC2(public) cftPublicEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: !FindInMap [cftEnvMap, !Ref cftEnv, InstanceType] SubnetId: !Ref cftPublicSubnet Monitoring: false SecurityGroupIds: - !Ref cftPublicEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: !Sub - cf-tutorial-public-ec2-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # SecurityGroup(public EC2) cftPublicEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub - cf-tutorial-public-ec2-sg-${env} - {env: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix]} GroupDescription: Allow SSH Access. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Sub - ${MyIpCidr} - {MyIpCidr: !Ref cftMyIpAddressCidr} Tags: - Key: Name Value: !Sub - cf-tutorial-public-ec2-sg-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # ElasicIp for public EC2 cftPublicEc2Eip: Type: AWS::EC2::EIP Properties: InstanceId: !Ref cftPublicEc2 # EC2(private) cftPrivateEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: !FindInMap [cftEnvMap, !Ref cftEnv, InstanceType] SubnetId: !Ref cftPrivateSubnet Monitoring: false SecurityGroupIds: - !Ref cftPrivateEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: !Sub - cf-tutorial-private-ec2-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # SecurityGroup(private EC2) cftPrivateEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub - cf-tutorial-private-ec2-sg-${env} - {env: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix]} GroupDescription: Allow SSH Access from Public EC2 Only. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Sub - ${privateIp}/32 - {privateIp: !GetAtt cftPublicEc2.PrivateIp} Tags: - Key: Name Value: !Sub - cf-tutorial-private-ec2-sg-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # ====================== # Outputs Statement # ====================== # IP Address Allocated to Public EC2 Outputs: cftPublicEc2Eip: Value: !GetAtt cftPublicEc2Eip.PublicIp
実行結果
上記をAIでレビューしてもらった結果、次のとおり回答されました。
ChatGPTらしく、すごい精度で回答が出てきました。
いくつか、ピックアップしてみます。
- 「ポジティブな点」で、いくつかほめてくれててうれしい^^。「3. リソースタグ付け」は「タグ付けしているからわかりやすいね」だ。ふむふむ。
- 「2. EIPリソースの属性」は「宣言が上下逆だ」と言っている。問題はないので、不要な指摘っぽい。
- 「3. パラメータの検証」は「CIDR形式かどうかチェックが必要だ」と言っている。さらに、「セキュリティリスクになる可能性がある」と警告しており、Ingress記述と併せて、この指定がネットワークのエンドポイントになっていることが理解できているのがすごい!
- 「4. 可用性ゾーンのハードコーディング」は「AZを固定しているからParameterやMappingで指定するようにしては?」と言ってる。ごもっとも。
まとめ
今回、NGの指摘ポイントを作り忘れたので、明確なNG指摘はありませんでしたが、それでもこれだけ改善提案を出してくれるのはありがたいです。実際にレビューアに提出する前に指摘を上げてくれるので、本人の気づきにもなりますし、開発の運用効率も上がりますね。