🐁

2022年に触った技術で年末駆け込み個人開発

2022/12/30に公開

はじめに

どうも、貧乏エンジニアリングが趣味なたこくんです。
2022年を振り返ると人生の中でも一大イベント転職がありましたが、
そんなことを振り返るよりも最後まで手を動かしたいということでこんな開発・記事を書きます。(といいつつ振り返り記事も書きたい。)

開発するアプリ

https://zenn.dev/takokun/articles/8562a278bb2010

2022年8月ごろに開発したGo言語のバージョン追加をSlack通知するアプリをまた作ります。
インフラまわりをちょこちょこっと変えます。

技術選定

Golang

https://go.dev/

大好きです。

kubernates

https://kubernetes.io/

2022年ちょっとずつ触る機会が増えてきました。
cronjobを使ってみたかったので採用します。

okteto

https://www.okteto.com/

無料でk8sの環境を手に入れることができます。

CockroachDB

https://www.cockroachlabs.com/

無料で5GBのPostgreSQL環境を手に入れることができます。

https://github.com/cockroachdb/cockroach

CockroachDBはGoで記述された分散SQLデータベースです。

slack api

https://api.slack.com/

通知に利用します。
本当はLINEを使ってみたかったですが調べる時間がなかったので断念します。

Terraform Cloud

https://cloud.hashicorp.com/products/terraform

CockroachDBのデプロイ差分を管理するのに利用します。
が、tfstateをアップロードできなかったので今回は断念しました。

構成図

全体像は以下のような形式となります。

ディレクトリ構成

モノレポで実装します。

.
├── Makefile
├── README.md
├── app // Go実装管理
├── aqua.yaml // 各種ツール管理
├── k8s // manifest管理
└── terraform // infra管理

appディレクトリは以下の構造になります。
おれおれクリーンアーキテクチャです。

app
├── Makefile // 各種コマンド
├── go.mod
├── go.sum
├── cmd
│   ├── migrate // マイグレーションコマンド
│   │   └── main.go
│   └── notice // 通知コマンド
│       └── main.go
└── internal
    ├── adapter
    │   ├── controller // コントローラー層
    │   │   └── tag.go
    │   ├── gateway // ゲートウェイ層
    │   │   ├── github.go
    │   │   ├── rdb.go
    │   │   └── tag.go
    │   └── notifier // 通知層(これはどうなんだろう...)
    │       ├── channel.go
    │       └── tag.go
    ├── domain
    │   ├── external // 外部依存のインターフェイス
    │   │   └── tag.go
    │   ├── model
    │   │   ├── tag // tagモデルのValue値
    │   │   │   ├── name.go
    │   │   │   ├── owner.go
    │   │   │   └── repo.go
    │   │   └── tag.go // tagドメインモデル(IDないので厳密にはドメインではない...)
    │   └── repository // 永続層のインターフェイス
    │       ├── github.go
    │       └── tag.go
    ├── driver // 外部との接続系
    │   ├── config
    │   │   └── config.go
    │   ├── database
    │   │   └── client.go
    │   └── slack
    │       └── client.go
    └── usecase // ユースケース層
        ├── interactor // ユースケース実装
        │   └── tag.go
        ├── port // ユースケースインターフェイス
        │   └── tag.go
        └── usecase.go // ユースケース

実装

ロジック自体はなんの工夫もない以下のものとなります。

  1. GitHubからタグ一覧を取得
  2. 保存済みのタグ一覧を取得
  3. 差分を比較
  4. 差分があった場合は
  5. 差分を保存
  6. 差分を通知
package interactor

import (
	"context"
	"fmt"
	"log"

	"github.com/takokun778/2022/internal/domain/external"
	"github.com/takokun778/2022/internal/domain/model"
	"github.com/takokun778/2022/internal/domain/repository"
	"github.com/takokun778/2022/internal/usecase/port"
)

type NoticeTag struct {
	github        repository.GitHub
	tagRepository repository.Tag
	tagExternal   external.Tag
}

var _ port.NoticeTag = (*NoticeTag)(nil)

func NewNoticeTag(
	github repository.GitHub,
	tagRepository repository.Tag,
	tagExternal external.Tag,
) *NoticeTag {
	return &NoticeTag{
		github:        github,
		tagRepository: tagRepository,
		tagExternal:   tagExternal,
	}
}

func (nt *NoticeTag) Execute(ctx context.Context, input port.NoticeTagInput) (port.NoticeTagOutput, error) {
	dst, err := nt.github.FindAll(ctx, input.Owner, input.Repo)
	if err != nil {
		return port.NoticeTagOutput{}, fmt.Errorf("failed to list tags: %w", err)
	}

	src, err := nt.tagRepository.FindAll(ctx, input.Owner, input.Repo)
	if err != nil {
		return port.NoticeTagOutput{}, fmt.Errorf("failed to list tags: %w", err)
	}

	diff := model.TakeTags(dst, src)

	if len(diff) == 0 {
		log.Printf("no tags changed\n")

		return port.NoticeTagOutput{}, nil
	}

	if err := nt.tagRepository.SaveAll(ctx, diff); err != nil {
		return port.NoticeTagOutput{}, fmt.Errorf("failed to save tags: %w", err)
	}

	for _, tag := range diff {
		url := fmt.Sprintf("https://github.com/%s/%s/releases/tag/%s", input.Owner, input.Repo, tag.Name)

		msg := fmt.Sprintf("released %s\n\n%s", tag.Name, url)

		if err := nt.tagExternal.Notice(ctx, msg); err != nil {
			return port.NoticeTagOutput{}, fmt.Errorf("failed to notice tag: %w", err)
		}
	}

	return port.NoticeTagOutput{}, nil
}

デプロイ

DockerHubにローカルからプッシュするコマンドは以下となります。

koを好んで使っています。

https://github.com/ko-build/ko

事前にko login -u ${USERNAME} -p ${PASSWORD} index.docker.ioを実行しておきます。

.PHONY: image
image:
	@(cd cmd/notice && ko build --sbom=none --bare --tags=latest ./ --platform=linux/amd64)

kubernates(okteto)へのデプロイするコマンドは以下となります。

事前にoktetoでトークンを発行しokteto login --token ${OKTETO_TOKEN}を実行しておきます。

DBへの接続情報やSlackのトークンなどは.gitignore対象の.envファイルに記述してenvsubstコマンドでマニフェストに埋め込むようにしています。(これは果たしてセキュアなのでしょうか...)

.PHONY: kapply
kapply:
	@envsubst '$$IMAGE','$$DATABASE_URL','$$GITHUB_OWNER','$$GITHUB_REPOSITORY','$$SLACK_TOKEN','$$SLACK_CHANNEL_ID' < k8s/cronjob.yaml > tmp.yaml
	@kubectl apply -f tmp.yaml
	@rm tmp.yaml

.PHONY: kdelete
kdelete:
	@kubectl delete -f k8s/cronjob.yaml

ちゃんと整備するのがめんどくさくなってローカルから実行しました...
GitOpsをちゃちゃっとできるように来年は手札を増やしたいですね。

動作確認

1時間ごとに実行するように以下でmanifestを作成しました。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: "2022"
spec:
  schedule: "0 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: "2022"
              image: ${IMAGE}
              imagePullPolicy: IfNotPresent
              env:
                - name: DATABASE_URL
                  value: ${DATABASE_URL}
                - name: GITHUB_OWNER
                  value: ${GITHUB_OWNER}
                - name: GITHUB_REPOSITORY
                  value: ${GITHUB_REPOSITORY}
                - name: SLACK_TOKEN
                  value: ${SLACK_TOKEN}
                - name: SLACK_CHANNEL_ID
                  value: ${SLACK_CHANNEL_ID}
          restartPolicy: Never

無事に以下のように実行結果がログ出力されました。

2022-12-30 08:00:10.97 UTC2022-27873120-zfnjk20222022/12/30 08:00:10 released go1
2022-12-30 09:00:00.00 UTC2022-27873180-z7xbs[pod-event]Successfully assigned 2022-27873180-z7xbs to gke-cloud-app-xxxx
2022-12-30 09:00:01.00 UTC2022-27873180-z7xbs[pod-event]Pulling image ""
2022-12-30 09:00:06.00 UTC2022-27873180-z7xbs[pod-event]Successfully pulled image "" in 5.018382494s
2022-12-30 09:00:06.00 UTC2022-27873180-z7xbs[pod-event]Created container 2022
2022-12-30 09:00:07.00 UTC2022-27873180-z7xbs[pod-event]Started container 2022
2022-12-30 09:00:09.80 UTC2022-27873180-z7xbs20222022/12/30 09:00:09 no tags changed

おわりに

今回作成したものは以下のリポジトリに置いておきます。

https://github.com/takokun778/2022

実装してみて

  • CICDまわりの手札を増やしたい
  • そろそろおれおれクリーンアーキテクチャ用のコード自動生成をしたい

と思いました。

今年何を触ったかなぁを振り返りながらアプリを開発するのも結構楽しかったです。
また来年もやろうと思います。

Discussion