CI/CDツールのDaggerを動かす

はじめに

CICDのパイプラインを記述する際はツールごとに独自のフォーマットや記述方法で記述する必要があるため移植性が低かったり、ローカルで試しに実行するのがなかなか難しかったりすることが多いと思います。DaggerはCICDの開発キッドで上記のような課題を削減することができます。 今回はこのDaggerをつかって簡単なパイプラインを作成し、ローカルで実行してみたいと思います。

Daggerとは

上記のようにDaggerはCICDのための開発キッドでCICDのパイプラインをCUEと呼ばれるようなGoogleで開発されて宣言的な言語で記述します。また特徴的なのが一度パイプラインを記述するとおおよそメジャーなCI環境で動かすことができます。そのため、CIのロックインを減らすことができます。また、ローカルマシーンで記述したパイプラインのテストやデバックを行うこともできます。
今回は他CIとのインテグレーションの機能は試しません。そちらについて知りたい人はこちらをご確認ください。

動かしてみる

動作環境

$ uname -srvmpio
Linux 5.14.0-1033-oem #36-Ubuntu SMP Mon Apr 4 15:15:49 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.4 LTS
Release:    20.04
Codename:   focal

$ docker version
Client: Docker Engine - Community
 Version:           20.10.14
 API version:       1.41
 Go version:        go1.16.15
 Git commit:        a224086
 Built:             Thu Mar 24 01:48:02 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.14
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.15
  Git commit:       87a90dc
  Built:            Thu Mar 24 01:45:53 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.5.11
  GitCommit:        3df54a852345ae127d1fa3092b95168e4a88e2f8
 runc:
  Version:          1.0.3
  GitCommit:        v1.0.3-0-gf46b6ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

インストール

インストールは以下のコマンドで行います。

$ cd /usr/local
$  curl -L https://dl.dagger.io/dagger/install.sh | sudo sh
$ dagger version
dagger 0.2.11 (0088c621) linux/amd64

これでDaggerのインストールは完了しました。 もし、Daggerを/user/local以外の場所にインストールしたい場合は、単純に自分がインストールしたいロケーションにcdしてください。
また、特定のバージョンをインストールしたい場合やLinux以外でのインストールを行いたい場合はこちらを確認してください。

コアコンセプト

Daggerのパイプラインははアクションと呼ばれるベーシックなブロックから構成されます。アクションは複雑な自動化の部分をカプセル化し抽象化することでシンプルなソフトウェアコンポーネントとして動作し、安全にシェアすることができます。
Actionはdagger doで実行することもできますし、もっと複雑なコンポーネントから実行することともできます。
Actionにはcore actioncomposite actionと呼ばれる2種類のActionが存在します。

core actionはプリミティブにDaggerエンジンに実装されているActionです。よりハイレベルなActionから利用されます。このActionを利用する場合はdagger.io/dagger/coreパッケージをインストールする必要があります。core actionのリファレンスはこちらを参照ください。
composite actionは他のActionから構成されるActionです。このActionはcorecomposite両方のActionから構成されることがあります。

Actionのライフサイクル

composite actionの以下のような4つのライフサイクルで実行されます。

  1. Definition
  2. Integration
  3. Discovery
  4. Execution

Definition

ActionはCUE Definitionと呼ばれるテンプレートで記述されます。
DefinitionはActionのインプットとアウトプット、サブアクションを定義します。

package main

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

// Write a greeting to a file, and add it to a directory
#AddHello: {
    // The input directory
    dir: dagger.#FS

    // The name of the person to greet
    name: string | *"world"

    write: core.#WriteFile & {
        input: dir
        path: "hello-\(name).txt"
        contents: "hello, \(name)!"
    }

    // The directory with greeting message added
    result: write.output
}

上記のサンプルはcore.#WriteFileと呼ばれるサブアクションを含んでいます。1つのActionは複数のサブアクションを組み込むことができます。

inputはその名と通りインフットで、Integrationのタイミングで値が決定します。外部からの入力を受け取ることができます。上記の例ではdirnameがインプットにあたります。
outputは逆に値を生成します。その値はIntegrationのタイミングで他のActionなどから参照することができます。上記の例ではresultがアウトプットにあたります。

dirresultなどのフィールドの名前に成約はありません。

Integration

Action definitionは直接実行できず、planに統合される必要があります。
planは実行コンテキストで、いかのようなことが定義します。

  • エンドユーザに提供されるAction
  • それらのタスクの依存関係
  • タスクと client システム(ローカルマシンとのインテグレーション)の相互作用

Actionの各CUEファイルはplanの CUR definitionにマージされ統合されます。
以下にplanの definition例を示します。

package main

import (
    "dagger.io/dagger"
)

dagger.#Plan & {
    // Say hello by writing to a file
    actions: hello: #AddHello & {
        dir: client.filesystem.".".read.contents
    }
    client: filesystem: ".": {
        read: contents: dagger.#FS
        write: contents: actions.hello.result
    }
}

上記の例では@AddHelloが直接planに統合されており、そして、core.#WriteFileは間接的に統合されています。
Planの詳細に関しては、後述します。

Discovery

planに統合されたActionはエンドユーザから利用することができます。
(ここではまだ下記のコマンドは実行できませんが、後ほど実行してみます)

$ dagger do --help
Usage: 
  dagger do  [flags]

Options


Available Actions:
 hello Say hello by writing to a file

(省略)

planにとうごうされたhello actionがDagger側から認識されます。

Execution

daggerから認識されたactionは、以下のように実行できます。
(ここではまだ下記のコマンドは実行できませんが、後ほど実行してみます)

$ dagger do hello

Hello WorldのActionを動かしてみる

コアコンセプトで利用したHello Worldのdefinitionを実行してみたいと思います。 まずはプロジェクトを初期化する必要があります。

$ mkdir helloworld && cd helloworld

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

次にdagger packageをインストールするためにdagger project updateコマンドを実行します。

$ dagger project update
10:21PM INF system | installing all packages...
10:21PM INF system | installed/updated package dagger.io@0.2.11
10:21PM INF system | installed/updated package universe.dagger.io@0.2.11

ここまでで準備が完了です。
先程のコアコンセプトで利用したActionとplanのDefinitionをそれぞれhello.cueplan.cueという名前で保存します。 するとDagger側からActionを認識できるようになります。

d$ dagger do --help
Usage: 
  dagger do  [flags]

Options


Available Actions:
 hello Say hello by writing to a file

(省略)

実際にActionを実行してみます。

$ dagger do hello
10:35PM INF upgrading buildkit    have host network=true version=v0.10.3                                                                                                             
[✔] client.filesystem.".".read                                                                                                                                                                                 0.1s
[✔] actions.hello.write                                                                                                                                                                                        0.0s
[✔] client.filesystem.".".write                                                                                                                                                                                0.0s

すると、hello-world.txtというファイルが作成されます。

$ ls
cue.mod  hello-world.txt  hello.cue  plan.cue

$ cat hello-world.txt 
hello, world!

次はplanを少しだけ書き換えて、greetする人を変えてみます。
plan.cueを以下のように変更します。

package main

import (
    "dagger.io/dagger"
)

dagger.#Plan & {
    // Say hello by writing to a file
    actions: hello: #AddHello & {
        dir: client.filesystem.".".read.contents
        //ここを追加
        name: "henoheno"
    }
    client: filesystem: ".": {
        read: contents: dagger.#FS
        write: contents: actions.hello.result
    }
}

再度Actionを実行してみます。

$ dagger do hello
[✔] client.filesystem.".".read                                                                                                                                                                                 0.1s
[✔] actions.hello.write                                                                                                                                                                                        0.0s
[✔] client.filesystem.".".write                                                                                                                                                                                0.0s

$ ls
cue.mod  hello-henoheno.txt  hello-world.txt  hello.cue  plan.cue

$ cat hello-henoheno.txt 
hello, henoheno!