Diary

Diary

日々学んだことをアウトプットする場として初めてみました

docker-compose で立てた psql の結果をローカルで受け取る

以下で紹介していることは基本は psql のオプションになります。

docker compose で立ち上げたコンテナに対して内部の DB に入るには以下のようにできます。

# postgresql: コンテナ名
# root: ユーザー名
# postgresql: 接続したい DB 名
docker compose exec postgresql psql -U root postgresql

出力結果をローカルから確認したり、そのままファイル出力させたい時は以下のように -c オプションで SQL を直接渡せます。

dc exec postgresql psql -U root postgresql -c "SELECT * FROM groups;"
 id  |   name
-----+-----------
   1 | group_01
   2 | group_02
   3 | group_3
   4 | group_4
   5 | group_5
   6 | group_6
   7 | group_7
   8 | group_8
   9 | group_9
  10 | group_10
--More--

件数が多く More が表示される場合は、さらに --pset=pager=off を指定してあげます。

dc exec postgresql psql -U root postgresql -c "SELECT * FROM groups;" --pset=pager=off
 id  |   name
-----+-----------
   1 | group_01
   2 | group_02
   3 | group_3
   4 | group_4
   5 | group_5
   6 | group_6
   7 | group_7
   8 | group_8
   9 | group_9
  10 | group_10
  11 | group_11
  ...

また、ヘッダーを表示したくない場合は -t オプションで指定できます。

$ dc exec postgresql psql -U root postgresql -c "SELECT * FROM groups;" --pset=pager=off -t
   1 | group_01
   2 | group_02
   3 | group_3
   4 | group_4
   5 | group_5
   ...

# ↓ のようにリダイレクトできる
$ dc exec postgresql psql -U root postgresql -c "SELECT * FROM groups;" --pset=pager=off -t > select_group"$(date "+%Y-%m-%d")"

ChatGPT で API サーバー(Golang, gin)をさくっと作る

最近は ChatGPT と一番会話をしてる気がします。。。

今回はそんな GhatGPT さんに Go の API サーバーの記述をお願いしたら、思ったより面白いことになったのでその紹介です。
(生成いただいた内容は github においてあります。)

TOC

やりとり

これが私の発言で

これが ChatGPT さんの発言です。

今回は、こちらで変更することなく一発で動くものを出していただきました!

API サーバーを立ててもらう

Go で API サーバーを作りたいのですが、以下の条件でコードを生成していただけますでしょうか?
・適切にパッケージ分割を行い、クリーンアーキテクチャに則った構成にする
・プロジェクトのパッケージ名は "chat-gpt" でお願いします
・web フレームワークは gin を使う
・ローカルには postgresql が 5432 のポート番号で起動している
 ・ユーザー名は root, パスワードは rootpassword, データベース名は postgresql とする
 ・groups という初期テーブルが存在しており、id が int で name が VARCHAR(20) で定義されている
・作って欲しいエンドポイントは次の 3 つです
 ・/health にアクセスした時は {"status": "ok"} という json を response body とする
 ・/hello にアクセスした時は name というクエリパラメーターを受け取り、{"greeting": "hello "} という json の response body とする。ただし には受け取ったクエリパラメーターを入れてください
 ・/group にアクセスした時は、postgresql に定義された groups というテーブルのグループ情報を全権取得し、json として返す。
  ・例: [{"id": 1, "name": "a"}, {"id": 2, "name": "abb"}]
よろしくお願いします。

以下のような Go 言語のコードを生成します:

出力された Go のコードを表示

package main

import (
    "database/sql"
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    _ "github.com/lib/pq"
)

type Group struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    db, err := sql.Open("postgres", "user=root password=rootpassword dbname=postgres sslmode=disable")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer db.Close()

    router := gin.Default()

    router.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "status": "ok",
        })
    })

    router.GET("/hello", func(c *gin.Context) {
        name := c.Query("name")
        c.JSON(http.StatusOK, gin.H{
            "greeting": fmt.Sprintf("hello %s", name),
        })
    })

    router.GET("/group", func(c *gin.Context) {
        rows, err := db.Query("SELECT * FROM groups")
        if err != nil {
            fmt.Println(err)
            c.AbortWithStatus(http.StatusInternalServerError)
            return
        }
        defer rows.Close()

        var groups []Group
        for rows.Next() {
            var group Group
            if err := rows.Scan(&group.ID, &group.Name); err != nil {
                fmt.Println(err)
                c.AbortWithStatus(http.StatusInternalServerError)
                return
            }
            groups = append(groups, group)
        }

        c.JSON(http.StatusOK, groups)
    })

    router.Run(":8080")
}


上記のコードは、gin という web フレームワークを使って API サーバーを構築しています。また、postgresql データベースに接続し、groups テーブルからグループ情報を取得するエンドポイントを定義しています。/health、/hello、/group エンドポイントで指定されたリクエストに応じて適切なレスポンスを返します。

ありがとうございます。
今は 1 つのファイルに記述いただいてますが、クリーンアーキテクチャを意識してパッケージを綺麗に分割することは可能ですか?

はい、可能です。クリーンアーキテクチャは、モジュール(パッケージ)を分割することによって、責任を明確にしてコードを保守しやすくすることを目的としています。上記のコードを複数のパッケージに分割することで、以下のようなメリットがあります:
・各パッケージは独立して開発やテストができるようになります。
・パッケージ間の依存関係が明確になり、責任の明確化が図れます。
API サーバーの構造がわかりやすくなります。
・修正や改善がしやすくなります。

以上が、クリーンアーキテクチャでパッケージ分割を行うメリットです。

先ほどの gin サーバーの例を、パッケージ分割して示していただけますか?

はい、分割した例を以下に示します。

出力された Go のコードを表示

main.go

package main

import (
    "github.com/gin-gonic/gin"
    "chat-gpt/controller"
    "chat-gpt/service"
)

func main() {
    db := service.ConnectDB()
    defer db.Close()

    r := gin.Default()
    controller.ApplyRoutes(r)
    r.Run()
}
service/database.go

package service

import (
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
)

func ConnectDB() *sql.DB {
    connStr := "user=root password=rootpassword dbname=postgresql sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    return db
}
controller/routes.go

package controller

import (
    "github.com/gin-gonic/gin"
    "chat-gpt/service"
)

func ApplyRoutes(r *gin.Engine) {
    db := service.ConnectDB()

    r.GET("/health", func(c *gin.Context) {
        c.JSON(200, gin.H{"status": "ok"})
    })

    r.GET("/hello", func(c *gin.Context) {
        name := c.Query("name")
        c.JSON(200, gin.H{"greeting": "hello " + name})
    })

    r.GET("/group", func(c *gin.Context) {
        var groups []service.Group
        rows, err := db.Query("SELECT * FROM groups")
        if err != nil {
            fmt.Println(err)
            c.JSON(500, gin.H{"error": "internal server error"})
            return
        }
        defer rows.Close()
        for rows.Next() {
            var g service.Group
            err := rows.Scan(&g.ID, &g.Name)
            if err != nil {
                fmt.Println(err)
                c.JSON(500, gin.H{"error": "internal server error"})
                return
            }
            groups = append(groups, g)
        }
        c.JSON(200, groups)
    })
}
service/model.go

package service

type Group struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

postgresql も立ててもらう

何から何まで誠に恐縮ですが、以下のような postgres を起動するための docker-compose ファイルを教えていただきたいです。 ・ローカルから見たい際に 5432 のポート番号で起動する ・ユーザー名は root, パスワードは rootpassword, データベース名は postgresql とする ・groups という初期テーブルが存在しており、id が int で name が VARCHAR(20) で定義されている よろしくお願いいたします。

以下のような docker-compose.yml ファイルになります。

version: "3"
services:
  postgresql:
    image: postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: rootpassword
      POSTGRES_USER: root
      POSTGRES_DB: postgresql
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

init.sql は初期テーブルとして groups を定義するための sql スクリプトになります。

CREATE TABLE groups (
    id SERIAL PRIMARY KEY,
    name VARCHAR(20) NOT NULL
);

docker-compose uppostgresql が起動すると、ローカルの 5432 ポートで接続できます。
また、init.sql で定義した初期テーブルを見ることができます。

README も書いてもらう

上の API サーバーの例で、README を書くとしたどんな内容になるか教えてください

以下は、上の API サーバーの例に対する README のサンプル内容です。

# API Server Example

このリポジトリは、RESTful API サーバーの例題です。以下の機能を持ちます。

- グループの一覧表示

## 動かし方

1. 事前準備として、PostgreSQL が必要です。以下のコマンドで PostgreSQL をインストールします。

<pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Debian-based systems</span>
$ sudo apt-get update
$ sudo apt-get install postgresql postgresql-contrib

<span class="synComment"># Red Hat-based systems</span>
$ sudo yum update
$ sudo yum install postgresql postgresql-server
</pre>

2. データベースを作成します。以下の SQL を実行します。

<pre class="code lang-sh" data-lang="sh" data-unlink>$ psql <span class="synSpecial">-U</span> postgres
<span class="synIdentifier">postgres</span>=# CREATE DATABASE api_server_example;
<span class="synIdentifier">postgres</span>=# \q
</pre>

3. テーブルを作成します。以下の SQL を実行します。

<pre class="code lang-sh" data-lang="sh" data-unlink>$ psql <span class="synSpecial">-U</span> postgres api_server_example
<span class="synIdentifier">api_server_example</span>=# CREATE TABLE groups <span class="synPreProc">(</span><span class="synSpecial">id SERIAL PRIMARY KEY, name TEXT NOT NULL</span><span class="synPreProc">)</span>;
<span class="synIdentifier">api_server_example</span>=# \q
</pre>

4. API サーバーを起動します。以下のコマンドを実行します。

<pre class="code lang-sh" data-lang="sh" data-unlink>$ go run main.go
</pre>

5. API を利用するには、以下のエンドポイントに対して HTTP リクエストを送信します。

- GET /groups: グループの一覧を取得します。

## 開発環境

Go 1.16.3

丁寧な README をありがとうございます。 README を書くときに意識していることはありますか?

Yes, here are some things to keep in mind when writing a README:

  1. Audience: Who is the intended audience for your project? Make sure the tone and language in your README is appropriate for that audience.
  2. Purpose: Clearly explain what your project does and what problem it solves.
  3. Getting started: Provide step-by-step instructions on how to set up and use your project.
  4. Features: List the key features and capabilities of your project.
  5. Screenshots or demo: Include screenshots or a demo video to give users a visual understanding of your project.
  6. Technical details: Explain the technical details of your project such as architecture, technology stack, and dependencies.
  7. Contributions: Provide information on how to contribute to your project, including contributing guidelines and a code of conduct.
  8. License: Include the license under which your project is released.
  9. Contact information: Provide contact information for users to reach out to you with questions or feedback.
  10. Future plans: Outline future plans for the project, including planned features and timelines.

そのほかに試したこと

  • godoc も生成してもらったが、英語で記載された
  • 同じ文章で試しても、パッケージ構成が結構変わる!
  • テストも書いてもらったりした!

確認

指示したことができているか、実動を確認してみます。

/health

完璧です。

/hello

完璧です。

/group

DB の結果が 0 件の時に null が帰ってきてしまいました。 これは完全に自分の考慮・指示漏れです。申し訳ございません。

中身がある時はきちんと表示できてそうです!

感想

  • 思った 3 倍はまともに書いてくれた
  • レスポンスが長文になった時、『続けてください』などと言えば続けてくれるが、結構な確率で内容が噛み合わなくなる
    • 他のパッケージとの整合性が取れなくなってしまう
  • 使いすぎると制限がかかるのがしんどい、辛い
  • 要約してもらうのとか面白い
  • 何故か突如英語で返信きて笑う

datadog で apache のログを収集してる際の /server-status を無視する

個人で勉強のために datadog を入れてみてラズパイで動かしているのですが、CPU 等を監視するために datadog-agent が 15 秒おきに /server-status?auto へアクセスを行なっているようです。
気づい時には 3 万 4000 ほど /server-status?auto へのログが datadog へと吸い上げられていました(それに対し意味のあるログは 1000 ほど)。

無駄なログはコストがかかるだけなので、今回はこれをあげないようにしてみました。

ざっと『apache のログに残さないようする』か『apache のログには残すが datadog には吸い上げないようにする』かの2通りあるかと思うのですが、今回は apache 単体で見ても意味のないログであることから『apache のログに残さないようする』方法を取ることにしました。
(エラーの場合は apache のエラーログには残る)

環境

$ apache2 -v
Server version: Apache/2.4.52 (Ubuntu)
Server built:   2021-12-28T20:18:12

$ cat /proc/cpuinfo | grep Model
Model           : Raspberry Pi 4 Model B Rev 1.4

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

apache で特定の URI へのログを残さないようにする

# とりあえず /server-status から始まるものをはじきたい
$ sudo vim /etc/apache2/apache2.conf

...
# common という名前をつけている。
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

# https://httpd.apache.org/docs/2.2/env.html#page-header
SetEnvIf Request_URI "^/server-status" dontlog
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log common env=!dontlog
...

$ sudo systemctl restart apache2

Go 言語で CLI ツールを爆速で作る方法

TOC

なぜ Go 言語で CLI ツールを作るのか

以下の 4 点がぱっと思いつきますね。

  1. 作ってて楽しい
  2. ロスコンパイル可能なので、複数プラットフォームへの対応が容易
  3. コンパイラ言語であるためそこそこ性能がでる
  4. 作ってて楽しい

また、みなさんがよく使ってるコマンドも、実は go でできてた、なんてことも多いと思います。

作り方

主に『サブコマンドを取る形式にするかどうか』で大きく 2 つに分類されます。

サブコマンドを取らない場合

サブコマンドを取らない場合というのは、ls コマンドのように『コマンド + オプション』で完結するタイプのコマンドを指しています。

この手のコマンドを作成する場合、公式の flag の package だけ事足りますが、『ロングオプションとショートオプションを手軽に記述したい』などの要望があるときは spf13/pflag などのパッケージを利用すると便利です。

サブコマンドを取る場合

サブコマンドを取る場合というのは、git コマンドのように『コマンド + サブコマンド + オプション』のように、複数のサブコマンドを取るタイプのコマンドを指しています。
git add xxx, git commit では、add, commit の部分をサブコマンドと呼ぶことにします)

この場合は、サードパーティのパッケージにおとなしく頼るのが吉です。
その中でも spf13/cobraurfave/cli が有名です。

どちらも数多くの使用例があり github を参考にできます。

cobra の使用例

『Projects using Cobra』みせびらかすように使用例が列挙されており、そのすごさが見てとれます。

kubectldocker, hugo, github-cli がこちらのパッケージを使って出来ているらしいです(強すぎ)。

urfave/cli の使用例

こちらは特に公式にまとめられてるとかはなかったのですが、ghq (や opencontainers/runc, ovh/cds)が実はそうみたいですね。

以前 git に関する cli を作った時はこちらを使いました。

実際にやってみる

今回は参考にできるリポジトリの多さから cobra でサブコマンド付きの cli を作成してみたいと思います。
(最近個人で作ってみてる cligithub にあげてます。)

setup

# go プロジェクトを初期化
$ go mod init gitlab.tokyo.optim.co.jp/takahiro.tominaga/cobra-example

# のちに使うため、cobra に加えて cli もインストールする
$ go install github.com/spf13/cobra-cli@latest
$ go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1

初期化

先ほどインストールした cobra-cli を使って、cobra プロジェクトとしての初期化を行います。

# init 前
.
├── README.md
└── go.mod

# init
$ cobra-cli init

# init 後: いくつかのファイルが生成されている
$ ls
.
├── LICENSE
├── README.md
├── cmd
│   └── root.go
├── go.mod
├── go.sum
└── main.go

この状態で適当に動かしてみましょう

$ go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

きちんと起動はしてそうですが、これ以上何もできないので、お待ちかねのサブコマンドを追加してみます。

サブコマンドの追加

先ほど同様、cobra-cli を使って追加していきます。

# cobra-cli add <コマンド名>
$ cobra-cli add hello

# cmd/hello が追加された
.
├── LICENSE
├── README.md
├── cmd
│   ├── hello.go
│   └── root.go
├── go.mod
├── go.sum
└── main.go

# hello サブコマンドを叩く
$ go run main.go hello
hello called

今回はこの hello コマンドを変更し、『name オプションに名前を取り、その名前を使って出力』させてみたいと思います。

cmd/hello の中身は以下のようになっています。
どうやら fmt.Println("hello called") を変更したらよさそうです。

package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

// helloCmd represents the hello command
var helloCmd = &cobra.Command{
Use:   "hello",
Short: "A brief description of your command",
Long: `デフォルトの説明長すぎてカット.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello called")
},
}

func init() {
rootCmd.AddCommand(helloCmd)
}

フラグを追加してみます。
フラグは spf13/pflag が使われているので、慣れているとサブコマンド無しの cli も作りやすいと思います。

// helloCmd represents the hello command
var helloCmd = &cobra.Command{
...
Run: func(cmd *cobra.Command, args []string) {
                // 先ほどのフラグを受け取る。
name, err := cmd.Flags().GetString("name")
if err != nil {
fmt.Println(err)
}
fmt.Printf("Hello %s!\n", name)
},
}

func init() {
        // **対象のコマンドに対し**フラグを追加
        // ロングオプション, ショートオプション, デフォルト値, 説明 の順
        // documentation: https://pkg.go.dev/github.com/spf13/pflag#StringP
helloCmd.Flags().StringP("name", "n", "john doe", "your name")
rootCmd.AddCommand(helloCmd)- [なぜ Go 言語で CLI ツールを作るのか](#なぜ-go-言語で-cli-ツールを作るのか)
}

実行してみます。

$ go run main.go hello --name kotlin
Hello kotlin!

# していないときはデフォルト値が入る
$ go run main.go hello
Hello john doe!

なお、オプションの型を bool にすると -a などのみで値を与えることが可能です。

注意点

2. クロスコンパイル可能なので、複数プラットフォームへの対応が容易 の良さを生かすためには、OS に固有の表現を使わないことが大切です。

例えば、パスの記述などでを string の結合で書くのはやめましょう。
パスや URL の扱いは、まともな言語なら標準のパッケージがあるのでそれを使うように意識します(com)。

pwd, _ := os.Getwd()
// dir + "/" + fileName とかで結合しない
joined := filepath.Join(pwd, "./README.md")
fmt.Printf("path to README.md: %s\n", joined)

実行例

# linux 環境
$ go run main.go readme
path to README.md: /home/tominaga/stamp/cobra-example/README.md

# windows 環境 (cmd)
>go run main.go readme
path to README.md: C:\Users\OPM004972\Documents\work\memo_dx\stamp\cobra-example\README.md

なお、ci 環境で複数プラットフォーム向けバイナリを作成するには goreleaser が便利です(gh-actions の例)。

jq: 階層化された json で UNIX タイムスタンプ を ISO 8601 形式の時間に変換する

TOC

目標

↓ の 1 つ目の json を 2 つ目の json のように変換することです。

.*_timestamp の key を持った部分をターゲットとし、key から _timestamp の suffix を削除、value を unixtimestamp から ISO 8601 形式に変換します。

{
  "start_timestamp": "1674256062",
  "end_timestamp": "1675256062",
  "settings": {
    "start_timestamp": "1674256062",
    "3rd_layer": {
      "start_timestamp": "1674256062",
      "comment": "hoge"
    }
  },
  "name": "me"
}
{
  "start": "2023-01-21T08:07:42+0900",
  "end": "2023-02-01T21:54:22+0900",
  "settings": {
    "start": "2023-01-21T08:07:42+0900",
    "3rd_layer": {
      "start": "2023-01-21T08:07:42+0900",
      "comment": "hoge"
    }
  },
  "name": "me"
}

結論

時間がない人のために、先に結論を書いておきます。

cat input.json |\
    jq 'walk(if type == "object" then with_entries( if (.key | test(".*_timestamp")) then (.value = (.value | strptime("%s") | strftime("%FT%T%z")) | .key |= gsub("_timestamp";"")) else . end ) else . end)'

解説

jq 初心者なので大した説明はできませんが、何を使ったのかだけ記載しておきます。

多階層を扱う

walkwith_entries を使います。

with_entries を挟むことで key, value の両方を取り扱いやすくし、それを walk で全階層に当てはめるイメージです。

# 公式の walk の例
$ cat in.json
[[4, 1, 7], [8, 5, 2], [3, 6, 9]]

# sort 関数を深い階層にまで当てはめる
$ cat | in.json | jq 'walk(if type == "array" then sort else . end)'
[[1,4,7],[2,5,8],[3,6,9]]

# 公式の walk の例 その 2
$ cat in.json
[ { "_a": { "__b": 2 } } ]
# .key |= sub( "^_+"; "") という関数を深い層にまで当てはめてるイメージ
# |= は便利なので覚えたい
$ cat in.json | jq 'walk( if type == "object" then with_entries( .key |= sub( "^_+"; "") ) else . end )'
[{"a":{"b":2}}]

なお、jq には上の sort ような便利なメソッドがたくさんあるので、jq を毎回適当に調べてるよって方はマニュアルをみてみると新たな発見があるかもしれません。

jq で時刻を扱う

最初何も知らなかったときは『date コマンド叩けたらな〜』とか思ってたのですが、jq コマンドの中に時刻を扱うメソッドをいくつか用意してくれているようです。

strflocaltime, strptime, strftime などの関数が C ライブラリから提供されていますが、どれも安定版として考えるのはやめてね、とのことです。 (以下のような注意書きがあり)

Note: these are not necessarily stable interfaces in jq, particularly as to their localization functionality.

しかし今回は気にせず使います。

strptime, strftime などの使い方の説明はしませんが、いくつか例だけ。

# timestamp が number で定義されてる場合
$ cat in.json
{"timestamp": 1675256062}

# strftime で可能
$ cat in.json | jq 'map_values(strftime("%FT%T%z"))'
{
  "timestamp": "2023-02-01T12:54:22+0900"
}

# timestamp が string で定義されてる場合
$ cat in_str.json
{"timestamp": "1675256062"}
# strftime だとエラーになる
$ cat in_str.json | jq 'map_values(strftime("%FT%T%z"))'
jq: error (at <stdin>:3): strftime/1 requires parsed datetime inputs
# 1 回 strptime を挟んでから strftime を通すといける
$ cat in_str.json | jq 'map_values(strptime("%s") | strftime("%FT%T%z"))'
{
  "timestamp": "2023-02-01T21:54:22+0900"
}

タイムゾーンの考慮が少し足りないかもしれないが、%z をつけたらローカルのタイムゾーンになるはず。

文字列操作

testgsub(文字列を切り取る)では、通常の正規表現が使えます。

メモ

  • |= は便利なので覚えておきたい
  • マニュアルはかなり詳しい

疑問

  • with_entries を挟むと key, value になるはずなのだが、どこで元の形式(初期の json)に戻ってるのかがあまり理解できてない

おまけ

このスクリプトは ChatGPT さんと相談させていただきながら作成しました。

そのままでバチっと動くものを出してくれることは少ないですが、非常に参考になる意見が多く刺激になります。
私の当面の目標は ChatGPT さんが一発で回答できるような質問力をつけることです。

pkg.go.dev にある godoc を強制的に更新する方法

前提

  1. 一度は情報を読み込めている
  2. tag プッシュしたものを反映させたい

方法

push したタグに対応する ↓ のリンクを開く

https://pkg.go.dev/github.com/{user_name}/{repository_name}@{tag_version}

{} には以下の情報を入れてください。

  • user_name: 個人の名前もしくは会社名
  • repository_name: リポジトリ
  • tag_version: タグの値

例えば以下のようなリンクになると思っています。

https://pkg.go.dev/github.com/android-project-46group/sgi-cli@v0.0.2

強制的に反映させるボタンを押す

godoc 上に情報が反映されていない場合、以下のように『Request "github.com/{user_name}/{repository_name}@{tag_version}"』というボタンが表示されているので、クリックして待ちます

以上です!

GitHub Actions でコミットできない時見直したい設定

はじめに

これらは 2023/1/16 現在の情報であり、GitHub の仕様変更によって将来変更を受ける可能性があります。

どんなエラーが出るか

権限不足でコミット等できなかった場合は、以下のようなエラーコードが表示されます(例)。
Invalid status code など、権限不足であることがぱっと見分かりにくい場合もあります。

Error: git push Invalid status code: 128

設定の変更方法

  1. repository の Settings > Actions > General のところに移動
  2. Workflow permissions の設定値を『Read and write permissions』に変更する

サードパーティGitHub Actions 等を使う場合は、しっかり内容を検討した上で許可するようにお願いします。

github-profile-summary-cards という GitHub Actions では、使用する Access Token に必要な権限については詳しく書いてくれているのですが、上記の設定ができていなかったために利用できないケースがありました。

おわりに

権限周りは慎重にいろんなところで細かく設定できるようにしてくれている分、詰まった時にどこが原因か分かりにくいと感じています。
問題が発生しないよう丁寧に GitHub Actions を使っていきたですね。