Daggerとは
公式サイトは以下です。
公式サイトによるとDaggerはCI/CDのためのポータブルな開発ツールとされています。
Daggerを使うメリットは以下です。括弧の中は筆者の意見/解釈です。
- CI/CDのパイプラインを一度書いておけば、どこでも同じパイプラインが実行できる (GitHub ActionsやCircle CIなどのツールによる記述の違いをある程度気にしなくて良くなりそう)
- CIツールのロックインを防ぐことができる (Github Actionsにインシデントが発生したらリリースが止まってしまう、みたいなことが防ぎやすくなる)
- CI/CDパイプラインのデバグを容易にする (ローカルでもCI環境を容易に構築できるので、リモートにいちいちpushしないと挙動が分からないとかいうことがなくなりそう)
とりあえずやってみる
インストール
❯ brew install dagger/tap/dagger
❯ type dagger
dagger is /usr/local/bin/dagger
公式ドキュメントが例を用意してくれているので、一旦それを試してみます。
❯ 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関連のファイルっぽいですね。
❯ 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
実行してみる。
❯ 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
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
package greeting
import "fmt"
func Greeting(name string) string {
return fmt.Sprintf("Hi %s!", name)
}
hello/greeting/greeting_test.go
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の詳しい説明は省きます。
作成したファイルは以下です。
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 ./...
が実行されると予想できます。
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のコマンドを実行する環境です。
...
// A standalone go environment to run go command
#Container: {
...
ローカルで実行してみる
まずはテストを実行してみます。
❯ 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
うまく実行できているようです。
次にビルドも試してみます。
❯ dagger do build
[✔] actions.build.container 1.0s
[✔] client.filesystem."./hello".read 0.0s
[✔] actions.build.container.export 0.0s
うまくできているようですね。
GitHub Actionsに組み込んでみます
以下のようなファイルを作成します。
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
適当なプルリクを作ってみて、ワークフローをトリガーさせます。ちゃんとテストが実行されてます!
ローカルで go test -v ./...
のコマンドを実行してみると↓のようになり、画像の40行~44行と同じログになっていることが分かります。
❯ 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で実行する内容が簡単に試せそうなので、確かにデバグはやりやすそう