1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語でのGitHub Actions(カスタムアクション)の初めての開発

Last updated at Posted at 2024-11-08
1 / 2

初めまして

hashi_03と申します。
GitHubはこちら: https://github.com/db-r-hashimoto
X(旧Twitter): https://x.com/ryu_has03

初めてのGoでのGitHub Action開発 - lgtm_print

GitHubのPull RequestやIssueのコメントに "LGTM" と記載があった場合、自動でランダムなLGTM画像を追加するカスタムアクション「lgtm_print」を開発しました。
開発の過程で学んだことや実装の工夫について紹介します。

ちなみに記事のベースはChatGPTに書かせています。生成AIっぽいなという表現はご了承ください。

実装したカスタムアクションはこちら

背景

GitHubのIssueやPR上で「LGTM (Looks Good To Me)」という表現が使われることが多いため、最近仕事でよく作ってるGitHub Actionsを使ってLGTM画像を投稿できるようにしようと考えました。
また、最近勉強しているのでGoを使用して開発してみました。

LGTMはLGTMeowを使いました。猫可愛いですよね、猫欲しい、、、

開発のポイント

1. ランダムにLGTM画像を取得する

まず、ランダムなLGTM画像を提供するために、LGTMeowのAPIを使用しました。
このAPIを使ってLGTM画像のリストを取得し、その中からランダムな画像を選択する処理を実装しました。

ちなみにAPIはどう知るのかはZennのこの記事を参考にしました。

package lgtmoon

import (
    "encoding/json"
    "errors"
    "math/rand"
    "net/http"
    "time"
)

type LGTMImage struct {
    ID       int    `json:"id"`
    ImageURL string `json:"imageUrl"`
}

func GetRandomLgtmImageURL() (string, error) {
    url := "https://lgtmeow.com/api/lgtm-images"
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return "", errors.New("failed to fetch images from LGTMeow API")
    }

    var images []LGTMImage
    if err := json.NewDecoder(resp.Body).Decode(&images); err != nil {
        return "", err
    }

    if len(images) == 0 {
        return "", errors.New("no images found")
    }

    rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
    randomIndex := rnd.Intn(len(images))
    return images[randomIndex].ImageURL, nil
}

2. コメントの編集でLGTM画像を追加する

次に、"lgtm"(大文字小文字分けなく)とコメントされた内容に対して、LGTM画像を既存のコメントに追加する処理を実装しました。
GitHub APIを活用し、既存コメントの内容を編集することで、画像を新しいコメントとして追加するのではなく、既存コメントに挿入する形にしています。

また、GitHub Actionsとして、コメントのIDとBodyを持ってくるようにしています。

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "strconv"
    "strings"

    "github.com/google/go-github/v48/github"
    "golang.org/x/oauth2"
    "github.com/db-r-hashimoto/lgtm_print/internal/lgtmoon"
)

func main() {
    token := os.Getenv("GITHUB_TOKEN")
    if token == "" {
        log.Fatal("GITHUB_TOKEN is not set")
    }

    ctx := context.Background()
    ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
    tc := oauth2.NewClient(ctx, ts)
    client := github.NewClient(tc)

    commentBody := os.Getenv("COMMENT_BODY")
    commentIDStr := os.Getenv("COMMENT_ID")
    owner := os.Getenv("OWNER")
    repo := os.Getenv("REPO")

    commentID, err := strconv.ParseInt(commentIDStr, 10, 64)
    if err != nil {
        log.Fatalf("Invalid COMMENT_ID: %v", err)
    }

    if strings.Contains(commentBody, "![LGTM]") {
        log.Println("LGTM image already present in the comment.")
        return
    }

    imageUrl, err := lgtmoon.GetRandomLgtmImageURL()
    if err != nil {
        log.Fatalf("Failed to fetch LGTM image: %v", err)
    }

    updatedComment := fmt.Sprintf("%s

![LGTM](%s)", commentBody, imageUrl)

    _, _, err = client.Issues.EditComment(ctx, owner, repo, commentID, &github.IssueComment{
        Body: &updatedComment,
    })
    if err != nil {
        log.Fatalf("Failed to edit comment: %v", err)
    }

    log.Println("LGTM image added to the existing comment successfully!")
}

実際テストで動かしたGitHub Actionsのyaml

コメントのBodyの内容でlgtmとある場合に動きます。
ただし、画像のaltをLGTMにしているため、![LGTM]という文字列の場合は処理自体Skipする仕組みを入れています。(Go側でも弾いているが念の為)

name: LGTM Bot

on:
  issue_comment:
    types: [created, edited]
  pull_request_review:
    types: [submitted, edited]
  pull_request_review_comment:
    types: [created, edited]

jobs:
  lgtm:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Check if comment contains 'lgtm'
        id: check_comment
        run: |
          echo "Comment body: ${{ github.event.comment.body }}"
          COMMENT_BODY="${{ github.event.comment.body }}"
          COMMENT_BODY_LOWER=$(echo "$COMMENT_BODY" | tr '[:upper:]' '[:lower:]')
          if [[ "$COMMENT_BODY_LOWER" != *"lgtm"* || "$COMMENT_BODY" == *"![LGTM]"* ]]; then
            echo "skip=true" >> $GITHUB_OUTPUT
          else
            echo "skip=false" >> $GITHUB_OUTPUT
          fi

      - name: Run LGTM Bot Action
        if: steps.check_comment.outputs.skip == 'false'
        uses: db-r-hashimoto/lgtm_print@v0.0.3
        with:
          github-token: ${{ secrets.GIT_TOKEN }}
          owner: db-r-hashimoto
          repo: lgtm_print
          comment_body: ${{ github.event.comment.body }}
          comment_id: ${{ github.event.comment.id }}

学び

  • Goでの開発の勉強になった
    新しく言語を学ぶ際は、何作ろうって考えてしまうことが多いですが、カスタムアクションなら日々使っているGitHubをもっと楽にできないかなと考えるので開発コストも高くないしとっつきやすかったですね。

  • GoでのAPIリクエストとJSONパース
    GoでのHTTPリクエストの送信とレスポンスのハンドリング、そしてJSONのデコードの基礎を学びました。
    net/httpパッケージで簡潔にAPIリクエストを扱えることや、encoding/jsonでレスポンスのJSONデータをGo構造体にマッピングできる便利さを実感しました。

  • GitHub APIクライアントライブラリの使用
    google/go-githubライブラリを利用することで、GoからGitHubのIssueやコメントの操作がスムーズに行えることを学びました。OAuth認証をセットアップしてからAPI操作を実行するまでの一連の流れを理解できました。

今後の展望

PRなどコメントに対して、ラベル貼りなどのカスタムアクションも作っていきたいですね。
例えばこんな感じ

あとはIssue含めてマイルストーンなどラベル貼れたら面白いのかなと思います。

それでは今回はここまでです。読んでいただきありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?