Open7

Dagger Learning

たまぬぎたまぬぎ

https://dagger.io/

Dagger

Dockerの創始者であるSolomon Hykes氏らが中心となって開発している CI/CD 環境構築ツール

公式ページを読んでみて特徴などをざっくり読んでみる。

A PORTABLE DEVKIT FOR CI/CD PIPELINES

Build powerful CI/CD pipelines quickly, then run them anywhere.

  • Give your developers parity between dev and CI environments
  • Test and debug your pipelines locally
  • Run the same pipeline on any CI environment without re-writes
  • Developed in the open by the creators of Docker

ローカルとCI環境での差分をいい感じに吸収して、CI/CDのテストや開発がしやすくなる感じかな?

Escape YAML hell

Wire actions together using CUE, a powerful configuration language developed at Google. CUE has all the features you wish YAML had: string interpolation, templating, static type checking, a complete package system, and more. And best of all, it can natively import and export YAML and JSON, for maximum compatibility with your existing tooling.

設定などはYAMLではなくCUE言語なるもので記述していくらしい。
CUE言語はGoogleが開発した設定用の言語で型チェックや文字列の補間、パッケージシステムなどがあるとのこと。
またJSON/YAMLをネイティブでサポートしているらしい

https://cuelang.org/

Create actions with any programming language.

Creating a custom action is straightforward. First write the code in your favorite programming language, no proprietary framework required. Then write a small CUE file specifying how to run your code in a container, and how to connect it to other actions. This allows for language-agnostic composition: actions written in different languages can be seamlessly wired into the same pipeline.

好きな言語でカスタムアクションを作成できる🎉
カスタムアクションをプログラミングしたら、あとはコンテナ内での実行方法と他のアクションとの接続方法をCUEで指示すればok
すごい!

Choose and reuse actions from a large catalog.

Stop re-inventing the wheel every time a pipeline needs to be created or updated. Dagger ships with a large catalog of actions, and you can add custom actions to reuse later.

車輪の再発明をしなくてもいいように、カスタムアクションを追加して再利用がしやすいようになっているとのこと。
CI/CDはYAMLで記述するのがDRYにするのが面倒だったりするのでこれは嬉しい

Test and debug instantly, on your local machine.

App developers don’t have to wait several minutes to catch a typo, and neither should you. Dagger lets you develop, test and debug your pipeline locally, so you can get the pipeline completed quicker and move on to putting out other fires.

パイプラインのテストやデバッグがローカルマシンで出来る!
CIファイルを編集してgit push して結果をさんざん待ってからtypoが発覚するみたいな悲しいことが無くなるの良さそう!

Run on any Docker-compatible runtime.

Real-world pipelines have to work on multiple platforms and they have to tie together fragmented tools. With Dagger, you can re-use pipelines on different platforms or build multi-platform pipelines, completely out-of-the-box.

マルチプラットフォームの差異をDockerが吸収してくれる?
今やDockerが動かない環境のほうが珍しいので良さそう

たまぬぎたまぬぎ

https://docs.dagger.io/getting-started

とりあえず getting started してみる

インストール

brewでインストールできる

$ brew install dagger/tap/dagger

インストールできたのでとりあえず helpやversion 見てみる

$ dagger help
A programmable deployment system

Usage:
  dagger [command]

Available Commands:
  do
  help        Help about any command
  project     Manage a Dagger project
  version     Print dagger version

Flags:
      --experimental        Enable experimental features
  -h, --help                help for dagger
      --log-format string   Log format (auto, plain, tty, json) (default "auto")
  -l, --log-level string    Log level (default "info")

Use "dagger [command] --help" for more information about a command.
$ dagger version
dagger 0.2.7 (18c19174) darwin/arm64
たまぬぎたまぬぎ

サンプルアプリでお試し

https://github.com/dagger/dagger

daggerのリポジトリにReactで作成されたTODOアプリがサンプルである

ドキュメントに従って dagger do build を実行してみる

$ git clone https://github.com/dagger/dagger
$ cd dagger
$ git checkout v0.2.7

$ cd pkg/universe.dagger.io/examples/todoapp
$ dagger do build

[] actions.build.run.script 2.4s
[] actions.deps 2.3s
[] client.filesystem."./".read 4.4s
[] actions.test.script 2.8s
[] actions.test 2.5s
[] actions.build.run 6.7s
[] actions.build.contents 0.6s
[] client.filesystem."./_build".write 0.1s

_build/ 以下に生成物が作成される

$ open _build/index.html
# TODOアプリがブラウザで開く
たまぬぎたまぬぎ

パイプラインの内容は todoapp.cue に記述されている

https://github.com/dagger/dagger/blob/main/pkg/universe.dagger.io/examples/todoapp/todoapp.cue

dagger do build を実行したときに実行されるパイプラインの定義は以下の箇所っぽい


		build: {
			run: bash.#Run & {
				input:   test.output
				mounts:  _nodeModulesMount
				workdir: "/src"
				script: contents: #"""
					yarn run build
					"""#
			}

			contents: core.#Subdir & {
				input: run.output.rootfs
				path:  "/src/build"
			}
		}

ここで build run: bash.#Run の inputに test.output が指定されているが、これは test アクションとの依存関係を表している?
dagger do build を実行したときに test アクションが実行されるのはここで指定しているから。

ビルドはdocker内で行われているのに最終的に _build ディレクトリに出てくるのは以下の定義?

	client: {
		filesystem: {
...
			"./_build": write: contents: actions.build.contents.output
		}

ここでの ./_build はホストマシンのパスで actions.build.contents.output をマウントしている感じなのかな?

actions.build.contents.output は以下で定義されている

		build: {
...

			contents: core.#Subdir & {
				input: run.output.rootfs
				path:  "/src/build"
			}
		}
たまぬぎたまぬぎ

dagger do build --log-format=plain --no-cache

--log-format=plain --no-cache をつけるとそれぞれ何やっているのかわかりやすい

12:07PM INF actions.build.run.script._write | computing
12:07PM INF actions.deps._dag."0"._dag."0"._op | computing
12:07PM INF client.filesystem."./".read | computing
12:07PM INF actions.deps._dag."2".script._write | computing
12:07PM INF actions.test.script._write | computing
12:07PM INF actions.deps._dag."2".script._write | completed    duration=1.7s
12:07PM INF actions.build.run.script._write | completed    duration=2.9s
12:07PM INF actions.test.script._write | completed    duration=3.3s
12:07PM INF client.filesystem."./".read | completed    duration=4s
12:07PM INF actions.deps._dag."0"._dag."0"._op | completed    duration=4s
12:07PM INF actions.deps._dag."0"._dag."1"._exec | computing
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 0.640 fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/main/aarch64/APKINDEX.tar.gz
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.088 fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/community/aarch64/APKINDEX.tar.gz
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.716 (1/4) Installing ncurses-terminfo-base (6.3_p20211120-r0)
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.737 (2/4) Installing ncurses-libs (6.3_p20211120-r0)
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.764 (3/4) Installing readline (8.1.1-r0)
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.788 (4/4) Installing bash (5.1.16-r0)
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.818 Executing bash-5.1.16-r0.post-install
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.820 Executing busybox-1.34.1-r3.trigger
12:07PM INF actions.deps._dag."0"._dag."1"._exec | #8 1.823 OK: 8 MiB in 18 packages
12:07PM INF actions.deps._dag."0"._dag."1"._exec | completed    duration=2.6s
12:07PM INF actions.deps._dag."0"._dag."2"._exec | computing
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.136 fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/main/aarch64/APKINDEX.tar.gz
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.354 fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/community/aarch64/APKINDEX.tar.gz
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.505 (1/8) Installing ca-certificates (20211220-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.534 (2/8) Installing nghttp2-libs (1.46.0-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.556 (3/8) Installing brotli-libs (1.0.9-r5)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.584 (4/8) Installing c-ares (1.18.1-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.604 (5/8) Installing libgcc (10.3.1_git20211027-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.626 (6/8) Installing libstdc++ (10.3.1_git20211027-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 1.660 (7/8) Installing nodejs (16.14.2-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 2.164 (8/8) Installing yarn (1.22.17-r0)
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 2.229 Executing busybox-1.34.1-r3.trigger
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 2.232 Executing ca-certificates-20211220-r0.trigger
12:07PM INF actions.deps._dag."0"._dag."2"._exec | #9 2.250 OK: 54 MiB in 26 packages
12:07PM INF actions.deps._dag."0"._dag."2"._exec | completed    duration=2.8s
12:07PM INF actions.deps._dag."0"._dag."3"._exec | computing
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 0.702 fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/main/aarch64/APKINDEX.tar.gz
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 0.914 fetch https://dl-cdn.alpinelinux.org/alpine/v3.15/community/aarch64/APKINDEX.tar.gz
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 1.039 (1/4) Installing libcurl (7.80.0-r0)
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 1.070 (2/4) Installing expat (2.4.7-r0)
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 1.092 (3/4) Installing pcre2 (10.39-r0)
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 1.119 (4/4) Installing git (2.34.2-r0)
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 1.304 Executing busybox-1.34.1-r3.trigger
12:07PM INF actions.deps._dag."0"._dag."3"._exec | #10 1.307 OK: 66 MiB in 30 packages
12:07PM INF actions.deps._dag."0"._dag."3"._exec | completed    duration=1.7s
12:07PM INF actions.deps._dag."1"._copy | computing
12:07PM INF actions.deps._dag."1"._copy | completed    duration=900ms
12:07PM INF actions.deps._dag."2"._exec | computing
12:07PM INF actions.deps._dag."2"._exec | #12 2.402 yarn config v1.22.17
12:07PM INF actions.deps._dag."2"._exec | #12 2.418 success Set "cache-folder" to "/cache/yarn".
12:07PM INF actions.deps._dag."2"._exec | #12 2.418 Done in 0.02s.
12:07PM INF actions.deps._dag."2"._exec | #12 2.520 yarn install v1.22.17
12:07PM INF actions.deps._dag."2"._exec | #12 2.579 [1/4] Resolving packages...
12:07PM INF actions.deps._dag."2"._exec | #12 2.894 [2/4] Fetching packages...
12:07PM INF actions.deps._dag."2"._exec | #12 17.19 [3/4] Linking dependencies...
12:07PM INF actions.deps._dag."2"._exec | #12 17.19 warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5".
12:07PM INF actions.deps._dag."2"._exec | #12 17.20 warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta".
12:07PM INF actions.deps._dag."2"._exec | #12 22.08 [4/4] Building fresh packages...
12:07PM INF actions.deps._dag."2"._exec | #12 22.84 Done in 20.32s.
12:07PM INF actions.deps._dag."2"._exec | completed    duration=25.2s
...

たまぬぎたまぬぎ

コメントつけていってみる

todoapp.cue
package todoapp

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

dagger.#Plan & {
	// _始まりはPrivateField
	_nodeModulesMount: "/src/node_modules": {
		dest:     "/src/node_modules"
		type:     "cache"
		contents: core.#CacheDir & {
			id: "todoapp-modules-cache"
		}

	}
	// daggerを実行するホストマシン関連の設定?
	client: {
		filesystem: {
			// Dockerコンテナに持ち込むファイルの定義?
			"./": read: {
				contents: dagger.#FS
				exclude: [
					"README.md",
					"_build",
					"todoapp.cue",
					"node_modules",
				]
			}
			// actions.build.contents.outputをホストマシンの _build/ に書き込む
			"./_build": write: contents: actions.build.contents.output
		}
		env: {
			APP_NAME:      string
			NETLIFY_TEAM:  string
			NETLIFY_TOKEN: dagger.#Secret
		}
	}
	actions: {
		deps: docker.#Build & {
			steps: [
				alpine.#Build & {
					// Alpineにインストールするパッケージの指定
					packages: {
						bash: {}
						yarn: {}
						git: {}
					}
				},
				// client.filesystem."./"で指定したファイルを /src にコピーしている?
				docker.#Copy & {
					contents: client.filesystem."./".read.contents
					dest:     "/src"
				},
				// yarn install の実行
				bash.#Run & {
					workdir: "/src"
					mounts: {
						"/cache/yarn": {
							dest:     "/cache/yarn"
							type:     "cache"
							contents: core.#CacheDir & {
								id: "todoapp-yarn-cache"
							}
						}
						_nodeModulesMount
					}
					script: contents: #"""
						yarn config set cache-folder /cache/yarn
						yarn install
						"""#
				},
			]
		}

		test: bash.#Run & {
			input:   deps.output
			workdir: "/src"
			mounts:  _nodeModulesMount
			script: contents: #"""
				yarn run test
				"""#
		}

		build: {
			run: bash.#Run & {
				input:   test.output
				mounts:  _nodeModulesMount
				workdir: "/src"
				script: contents: #"""
					yarn run build
					"""#
			}

			contents: core.#Subdir & {
				input: run.output.rootfs
				path:  "/src/build"
			}
		}

		deploy: netlify.#Deploy & {
			contents: build.contents.output
			site:     client.env.APP_NAME
			token:    client.env.NETLIFY_TOKEN
			team:     client.env.NETLIFY_TEAM
		}
	}
}