ポータブルでクールなCI/CD開発ツールのDaggerを試してみたら最高に良かった！

# Daggerとは

公式サイトは以下です。

https://docs.dagger.io/

公式サイトによるとDaggerはCI/CDのためのポータブルな開発ツールとされています。

Daggerを使うメリットは以下です。括弧の中は筆者の意見/解釈です。

- CI/CDのパイプラインを一度書いておけば、どこでも同じパイプラインが実行できる (GitHub ActionsやCircle CIなどのツールによる記述の違いをある程度気にしなくて良くなりそう)
- CIツールのロックインを防ぐことができる (Github Actionsにインシデントが発生したらリリースが止まってしまう、みたいなことが防ぎやすくなる) 
- CI/CDパイプラインのデバグを容易にする (ローカルでもCI環境を容易に構築できるので、リモートにいちいちpushしないと挙動が分からないとかいうことがなくなりそう)

# とりあえずやってみる

## インストール

```bash
❯ brew install dagger/tap/dagger

❯ type dagger
dagger is /usr/local/bin/dagger
```

公式ドキュメントが例を用意してくれているので、一旦それを試してみます。

```bash
❯ git clone https://github.com/dagger/dagger
Cloning into 'dagger'...
remote: Enumerating objects: 24380, done.
remote: Counting objects: 100% (24379/24379), done.
remote: Compressing objects: 100% (9164/9164), done.
remote: Total 24380 (delta 13415), reused 23957 (delta 13215), pack-reused 1
Receiving objects: 100% (24380/24380), 10.87 MiB | 8.80 MiB/s, done.
Resolving deltas: 100% (13415/13415), done.

❯ cd dagger

❯ git checkout v0.2.4

❯ cd pkg/universe.dagger.io/examples/todoapp
```

何があるかチェックしてみる。　`todoapp.cue` が見慣れないファイルで、Dagger関連のファイルっぽいですね。

```bash
❯ ls
README.md  package.json  public  src  todoapp.cue  yarn.lock

❯ tree .
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.js
│   ├── components
│   │   ├── FilterButton.js
│   │   ├── Form.js
│   │   └── Todo.js
│   ├── index.css
│   └── index.js
├── todoapp.cue
└── yarn.lock

3 directories, 16 files
```

実行してみる。

```bash
❯ dagger do build
[✔] actions.build.run.script                                                                                                                                                                                                                              0.1s
[✔] actions.test.script                                                                                                                                                                                                                                   0.0s
[✔] actions.deps                                                                                                                                                                                                                                         53.7s
[✔] client.filesystem."./".read                                                                                                                                                                                                                           0.2s
[✔] actions.test                                                                                                                                                                                                                                          1.2s
[✔] actions.build.run                                                                                                                                                                                                                                    11.3s
[✔] actions.build.contents                                                                                                                                                                                                                                0.0s
[✔] client.filesystem."./_build".write                                                                                                                                                                                                                    0.1s
```

`open _build/index.html` を打つとブラウザでビルドされたアプリが起動するのが確かめられます！ `dagger do build` でビルドしてくれることがなんとなく分かりました。

# もっと簡単な例で試してみる

↑の例の `todoapp.cue` の内容が少し複雑だったので、もっと簡単な例で試してみることにしました。Goで適当な関数を書いてテストとビルドを試してみましょう。またGitHub Actionsに組み込んでみます。

https://github.com/k3forx/dagger_example に全部のコードを置いてます。

## Goの関数を書く

3つのファイルを用意します。

- `hello/main.go` 

```golang
package main

import (
        "fmt"
        "os"

        "github.com/k3forx/dagger_example/hello/greeting"
)

func main() {
        name := os.Getenv("NAME")
        if name == "" {
                name = "John Doe"
        }
        fmt.Println(greeting.Greeting(name))
}
```

- `hello/greeting/greeting.go`

```golang
package greeting

import "fmt"

func Greeting(name string) string {
        return fmt.Sprintf("Hi %s!", name)
}
```

- `hello/greeting/greeting_test.go`

```golang
package greeting

import "testing"

func TestGreeting(t *testing.T) {
        name := "Dagger Test"
        expect := "Hi Dagger Test!"
        value := Greeting(name)

        if expect != value {
                t.Fatalf("Hello(%s) = '%s', expected '%s'", name, value, expect)
        }
}
```

## テストとビルドを実行するdaggerコマンドを定義する

`dagger do ...` の `...` の部分に任意のプロセスを実行するコマンドを定義することができます。定義するためのファイルはCUEという言語で書く必要があります。ここではCUEの詳しい説明は省きます。

作成したファイルは以下です。

```CUE
package main

import (
    "dagger.io/dagger"

    "universe.dagger.io/go"
)

dagger.#Plan & {
    client: filesystem: "./hello": read: contents: dagger.#FS

    actions: {
        test: go.#Test & {
            source:  client.filesystem."./hello".read.contents
            package: "./..."
        }

        build: go.#Build & {
            source: client.filesystem."./hello".read.contents
        }
    }

}
```

- Daggerで記述するファイルは常に `dagger.#Plan` で始める必要があります
- `client` のファイルシステムとやりとりができて、ファイルを読み込んだり、結果をファイルに書き込んだりすることができます
  - 今回の場合は、`./hello` ディレクトリを読み込むことが指定されてます。 `dagger.#FS` はファイルであることを示す型になります 
- `actions` では実際に実行する操作を定義することができます
  - goの場合は、専用のパッケージが https://github.com/dagger/dagger/tree/main/pkg/universe.dagger.io/go に用意されてます

例えば、 https://github.com/dagger/dagger/blob/main/pkg/universe.dagger.io/go/test.cue は以下のような内容になってます。

`package` は文字列型の引数として使用することができて、指定されていない場合は `.` が適用されます。そして、`#Container` の中身に `package` が `args` として渡されていて、`name` が `go` で、`flags` が `test: true`, `"-v": true` として渡されています。

今回は `package: "./..."` として引数を渡しているので、コマンドレベルでは　`go test -v ./...` が実行されると予想できます。

```CUE
package go

// Test a go package
#Test: {
	// Package to test
	package: *"." | string

	#Container & {
		command: {
			name: "go"
			args: [package]
			flags: {
				test: true
				"-v": true
			}
		}
	}
}
```

そして https://github.com/dagger/dagger/blob/main/pkg/universe.dagger.io/go/container.cue もみてみると以下のような記述があります。これ以上は深追いはしないですが、 `#Container` はgoのコマンドを実行する環境で、 `#Test` から渡ってきた引数に基づいてgoのコマンドを実行する環境です。


```CUE
...
// A standalone go environment to run go command
#Container: {
...
```



## ローカルで実行してみる

まずはテストを実行してみます。

```bash
❯ dagger project init
Project initialized! To install dagger packages, run `dagger project update`

❯ dagger project update
Project updated

❯ dagger do test
[✔] actions.test                                                                                                          1.7s
[✔] client.filesystem."./hello".read                                                                                      0.0s
```

うまく実行できているようです。

次にビルドも試してみます。
```bash
❯ dagger do build
[✔] actions.build.container                                                                                                1.0s
[✔] client.filesystem."./hello".read                                                                                       0.0s
[✔] actions.build.container.export                                                                                         0.0s
```

うまくできているようですね。

## GitHub Actionsに組み込んでみます

以下のようなファイルを作成します。

```yaml
name: test

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Test
        uses: dagger/dagger-for-github@v2
        with:
          version: 0.2
          cmds: |
            project init
            project update
            do test
```

適当なプルリクを作ってみて、ワークフローをトリガーさせます。ちゃんとテストが実行されてます！ :tada: 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349999/0a18c256-0d6c-6fac-24d8-70e7fc1f7f39.png)

ローカルで　`go test -v ./...`　のコマンドを実行してみると↓のようになり、画像の40行~44行と同じログになっていることが分かります。

```bash
❯ go test -v ./...
?       github.com/k3forx/dagger_example/hello  [no test files]
=== RUN   TestGreeting
--- PASS: TestGreeting (0.00s)
PASS
ok      github.com/k3forx/dagger_example/hello/greeting
```

# まとめ

- Daggerを使ってテストをビルドを実行する簡単なCIを構築してみた
- どのようにテストを実行するかはCUEだけに依存していて、Github ActionsやCircle CIには実行するコマンドだけを用意すれば良いので、複数のrepoで共通のCI/CDを実行した時とかは便利そう
- ローカルでGithub Actionsで実行する内容が簡単に試せそうなので、確かにデバグはやりやすそう