daggerによるContainerBuildまでの道のり(未完)

# はじめに
なんか、Dockerを作った人がCIの抽象レイヤのプロダクトを作ってパブリックベータになったというので、取りあえずは触ってみたいと思い、触ってみました。

https://dagger.io/

目的としては、
- 現在、GithubActions上で動いているCI/CDパイプラインがベンダーロックインにならないようにしたいということ
- ローカル環境でも同じものが動くということは、開発上のガイドレールを設定しやすいこと
- GithubActionsのリソースを使わなくてもいい場面が増えるなら、コストが減るかな？

という感じです。

# GoalSetting
1. (Local)HalloWorldを出す
1. (Local)ApplicationのBuild/テストを行う
1. (Local)ContainerBuildまで

# 結果
ApplicationBuild/TestまではJavaでも行けました。ですが、コンテナビルドまではできず。多分、DockerFileを使ったりする形では行けますが、SpringBootのイメージビルドやJibPluginを使った場合はまだ厳しそう。

# 前提条件
- ローカル環境にDaggerがInstallされていること

https://docs.dagger.io/1200/local-dev

を参照して、動かせるところまで確認しておくこと。
※いきなり依存関係とかがあって、GettingStartなのにどう動いているかわかんない。

# HelloWorldまでの道のり

## 右往左往する
以下は右往左往した経緯です。興味のある方はどうぞ。
結果が見たい方は飛ばして下さい。

<details>
<summary>右往左往の歴史</summary>

### 定義ファイル(cue)を作成する
コアコンセプトを見て、まずは作成する。
```helloworld.cue
package test

import (
    "dagger.io/dagger"
    "dagger.io/dagger/core"
)

daggar.#Plan & {
  actions: {
    hello: {
      run: bash.#Run & {
        script: contents: #"""
          echo "HelloWorld"
        """#
      }
    }
  }
}
```
作成したので、適当なフォルダに保存して見様見真似で実行してみました。
`dagger do hello`
そうすると、Projectがないから、初期化しろと怒られた。
```
failed to load plan: dagger project not found. Run `dagger project init`
```

### 初期化(init)
`dagger project init`
適当なフォルダを作成し、上記コマンドを実行するとフォルダが作られました。
先ほどのhelloworld.cueを含むと以下のような感じ。
```
.
├── cue.mod
│   ├── module.cue
│   ├── pkg
│   └── usr
└── helloworld.cue
```
で、実行しようとすると、(Golangを知ってる人は想定されそう)、Updateしろと怒られた。
```
failed to load plan: import failed: cannot find package "dagger.io/dagger":
: running `dagger project update` may resolve this
```

### PkgのUpdateと整理
`dagger project update`
とすると、pkg内に色々なものが保存されました。BashのPkgが足りてないので、
```
"universe.dagger.io/bash"
```
をImportに追記して実行。CoreのPkgを使ってないということで、Coreも削除。ここらへん、使ってないものを残すなというGolang製らしい感じを受けます。

### いざ、実行
実行すると
```
dagger do build
[✔] actions.hello.run.script                        0.0s
[✗] actions.hello.run                               0.0s
6:13PM FTL failed to execute plan: task failed: actions.build.run._exec: invalid FS at path "actions.build.run._exec.input": FS is not set
```
FSがないと怒られました。なんで？
なお、contens:を削除すると、引数が足りないというErrorになりました。

### bash.cueを読んでみる
github上で公開されているので、確認してみる。
``` bash.cue(一部)
\#Run: {
  script: {
    directory:
    filename: string
  } | {
    contents: string
    _filename: "run.sh"
    _write:    core.#WriteFile & {
	  input:      dagger.#Scratch
      path:       _filename
	  "contents": contents
	}
  }
}
```
とあるので、今回の形だと、ファイルを読み込ませるのではなくStringで読み込ませたものをshとして動かしているよう。その際に、一時的にファイルに書き出している。なので、inputとしてFSが必要の様子。仮でということで
```helloworld.cue
dagger.#Plan & {
    actions: {
        hello: {
            dir: dagger.#FS
            run: bash.#Run & {
                input: dir
                workdir: "/tmp"
                script: contents: #"""
                    echo "HelloWorld"
               """#
            }
        }
    }
}
```
とすると
```
failed to load plan: actions.hello.run.input: field not allowed: rootfs:
```
となる。そりゃ、FSが定義されてないので、rootになるんですかね。

### FSを確認する
```typs.cue
// For example:
//  - The root filesystem of a container
//  - A source code repository
//  - A directory containing binary artifacts
// Rule of thumb: if it fits in a tar archive, it fits in a #FS.
#FS: {
	$dagger: fs: _id: string | null
}
```
となっているので、自動的にコンテナのRootFsになっている気がするけど、ダメだったので、ファイルに切り替える。
の時に気づく、そういえば、動かすコンテナベースの指定をしていなかった。DaggerはDocker上で動かすことで各CIツールの環境を吸収しているので、必要？DefaultではAlpineを自動で呼ぶようだが、Bashを使うとどうもFS周りのためか失敗する。

### depsで指定する。
```helloworld.cue
package helloworld

import (
    "dagger.io/dagger"
    "universe.dagger.io/docker"
    "universe.dagger.io/bash"
)

dagger.#Plan & {
    actions: {
        deps: docker.#Pull & {
            source: "ubuntu:latest" 
        }
        hello: {
            run: bash.#Run & {
                input: deps.output
                script: contents: #"""
						echo "HelloWorld"
						"""#
            }
        }
    }
}
```
とすることで、実行まで行けたが、`--log-format=plain`を付与してもConsoleに文言が出てこない。
https://github.com/dagger/dagger/blob/main/pkg/universe.dagger.io/examples/helloworld/helloworld.cue
だと、actionsにalpineを指定してExecでechoコマンドを走らせているが、今後を考えるとBashなりで動かしたい。depsではなくサンプルと同じようにubuntuを指定したが、以下のErrorが出てくる。
```
failed to load plan: actions.hello.run.input: field not allowed: rootfs:
```
出る場合と出ない場合があるので、daggerでCache次第と判断して実行時にOption(キャッシュ無し)を追加。
</details>

## HelloWorldができた
```helloworld.cue
package helloworld

import (
    "dagger.io/dagger"
    "universe.dagger.io/docker"
    "universe.dagger.io/bash"
)

dagger.#Plan & {
    actions: {
        // 実行環境の作成。BuildせずBaseImageをPullするだけにする。
        deps: docker.#Pull & {
            source: "ubuntu:latest" 
        }
        // HelloWorld
        hello: {
            run: bash.#Run & {
                input: deps.output
                script: contents: #"""
						echo "HelloWorld"
						"""#
            }
        }
    }
}
```
で
`dagger do hello --log-format=plain --no-cache`
を実行すると
```
11:45AM INF actions.hello.run.script._write | computing
11:45AM INF actions.deps._op | computing
11:45AM INF actions.hello.run.script._write | completed    duration=0s
11:45AM INF actions.deps._op | completed    duration=1.1s
11:45AM INF actions.hello.run._exec | computing
11:45AM INF actions.hello.run._exec | completed    duration=200ms
11:45AM INF actions.hello.run._exec | #4 0.142 HelloWorld
```
こうなりました。

# ApplicationBuildを実装する
他の方の記事だと、Goだと特に実行環境を指定せずに`go build`も実行できていたよう。DefaultImageがAlpineで、コマンドを実行する際に必要なpackageを導入してからRunを動かしてくれる様子。PythonもPkgにありますので対応していそうです。
弊社だとJava(SpringBoot)が多いので、そちらを考えてみます。
SpringBootのHelloWorldのサンプルを準備して、さあ、行くぞ。

https://spring.io/quickstart

```springtest.cue
package springtest

import (
    "dagger.io/dagger"
    "dagger.io/dagger/core"
    "universe.dagger.io/docker"
    "universe.dagger.io/bash"
)

dagger.#Plan & {
    client: {
        filesystem:{
            // ローカルの現在のディレクトリをコンテナから読み出すRead権限を付与
            "./": read: {
                contents: dagger.#FS
                exclude: [
                    "build",
                    "springtest.cue",
                ]
            }
            // _buildにBuild結果を書き出すWrite権限を付与
            "./_build" : write: contents: actions.build.contents.output
        }
    }
    actions: {
        // 実行環境の作成。build用なのでGradleをベースにする
        deps: docker.#Build & {
            steps: [
                docker.#Pull & {
                    source: "gradle:7.4.2-jdk11" 
                },
                // 現在のディレクトリを実行コンテナにコピーする
                docker.#Copy & {
                    contents: client.filesystem."./".read.contents
                    dest: "/app"
                }
            ]
        }
        // build
        build: {
            run: bash.#Run & {
                input: deps.output
                workdir: "/app"
                script: contents: #"""
						./gradlew build
						"""#
            }
			contents: core.#Subdir & {
				input: run.output.rootfs
				path:  "/app/build"
			}
        }
    }
}
```
`dagger do build --log-format=plain`
を実行することにより、_buildフォルダがローカルに作成されて、ビルドされたjarファイルが保存されました。もちろん、
`java -jar ./_build/libs/dagger_spring-0.0.1-SNAPSHOT.jar`
でHelloWorldが動くことも確認済みです。

# Container Buildを追加する
`gradle bootBuildImage`はLocalにDockerがないと上手く動かない。jibはイメージビルドまでは通るが、LocalのDockerが認識されないとDockerHubにつなごうとする様子。
なので、ビルドしたjarをDockerfileを使用する形でコンテナ化すれば行けそう感じがします。けど、

**GIVE UP!!**

# 補足
Outputの形式が`core.#Pull`だとdagger.#FS、`docker.#Build`だと#imageとなっていて、どうも実行環境が用意されているものから外れると、難易度が高くなります。
`bash.#Run`に#FSの定義が必要なので、`core.#Pull`を使用して標準のalpineImage以外を使用してみたところ、#FSが定義されてないとbash.#Runがエラーを吐いたので、？？となりました。

# まとめ
GoとかPythonなら現状でも使えそうです。(イケてるとこはJavaなんて使ってないだろ？ということか※偏見）

まだ0.2なので、今後に色々とPkgが増えることとドキュメント群が充実したら再挑戦したいところです。