Formを作る
Formは、リソースの設定項目と、その設定が意味する振る舞いを記述するものです。 定義をパッケージにまとめると、公開元、クライアント、Hostが同じ内容を検証できます。 ここでは小さな定義からパッケージを組み立て、入力の検証と改変の検出まで試します。
まず動かす
所要時間の目安は5分です。GoとGit、初回のソース・依存取得にはネットワーク接続が必要です。 既にこのリポジトリを取得している場合は、そのルートで最後の2行を実行してください。
git clone https://github.com/tako0614/takoform.git
cd takoform
go mod download
go test -v ./formpackage -run '^ExampleVerifyFS$' -count=1テストは次の出力を検証し、PASS で終了します。
GreetingPolicy 1
changed payload rejected: true例は架空の GreetingPolicy を使います。ローカルの一時データだけを扱い、 署名、パッケージの公開、Host上のリソース作成は行いません。
設定と意味を定義する
この例の設定は、40文字以内の挨拶の接頭辞です。作成時にそのまま保存し、 更新時に置き換え、削除時に取り除く方針を定義します。実行用のエンドポイントはありません。
const exampleDefinition = `{
"apiVersion": "resources.publisher.example",
"kind": "GreetingPolicy",
"definitionVersion": "0.1.0",
"title": "Greeting policy authoring example",
"description": "Synthetic authoring fixture. The prefix is stored exactly as supplied; updating replaces it and deleting removes the policy. No runtime endpoint is provided.",
"role": "policy",
"requiresHostApi": "forms.takoform.com/v1",
"desiredSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {"prefix": {"type": "string", "minLength": 1, "maxLength": 40}},
"required": ["prefix"]
},
"lifecycleCapabilities": ["create", "read", "update", "delete", "observe"]
}`desiredSchema は入力の形を検証します。それだけでは「更新で何が変わるか」や 「失敗後に再試行できるか」は決まりません。実用のFormでは、ライフサイクル、失敗時の扱い、 必要なInterfaceやBindingまで定義します。同じFormとして提供できる振る舞いの範囲は Form Definition と 移植性の範囲 を参照してください。
パッケージを組み立てて検証する
次のコードは上の定義と入力を検証し、definition.json と package-index.json を持つ 仮想ファイルシステムを組み立てます。VerifyFS は一時ディレクトリを使って内容を検証し、 処理後にそのディレクトリを削除します。
func ExampleVerifyFS() {
raw := []byte(exampleDefinition)
definition, err := formpackage.ValidateDefinition(raw)
if err != nil {
panic(err)
}
if err := formpackage.ValidateDesiredInstance(definition.DesiredSchema, map[string]any{"prefix": "Hello"}); err != nil {
panic(err)
}
schemaDigest, err := formpackage.DigestCanonicalJSON(raw)
if err != nil {
panic(err)
}
index, err := json.Marshal(map[string]any{
"apiVersion": formpackage.VersionlessFamilyPackageAPIVersion,
"kind": "FormPackage",
"formRef": formpackage.FormRef{
APIVersion: definition.APIVersion, Kind: definition.Kind,
DefinitionVersion: definition.DefinitionVersion, SchemaDigest: schemaDigest,
},
"definitionPath": "definition.json",
"files": []map[string]any{{
"path": "definition.json", "mediaType": formpackage.DefinitionMediaType,
"size": len(raw), "digest": formpackage.DigestBytes(raw),
}},
})
if err != nil {
panic(err)
}
files := fstest.MapFS{
"definition.json": &fstest.MapFile{Data: raw},
"package-index.json": &fstest.MapFile{Data: index},
}
report, err := formpackage.VerifyFS(files, ".")
if err != nil {
panic(err)
}
fmt.Println(report.FormRef.Kind, report.FileCount)
// A stale index must not validate a changed payload.
files["definition.json"].Data = append(append([]byte{}, raw...), '\n')
_, err = formpackage.VerifyFS(files, ".")
fmt.Println("changed payload rejected:", err != nil)
// Output:
// GreetingPolicy 1
// changed payload rejected: true
}schemaDigest は正規化した定義を識別します。一方、索引の各ファイルの digest と size は収録したバイト列そのものを検証します。例の最後では定義ファイルに改行を足し、 索引を更新しないまま再検証して、改変が拒否されることを確認しています。
ファイルとして配布するときも同じ定義と索引を保存し、すべての収録ファイルと参照先を揃えます。 索引の形式と計算規則は Form Package が基準です。
公開前に揃えるもの
- 公開元が管理する名前空間と、Formの用途・振る舞いを決める。
- 設定、更新・削除、エラー、再試行の規則と、それを確認するテストを用意する。
- 互換性の規則 に従ってバージョンを決め、公開済みの内容を上書きしない。
- 署名と失効の仕様 に沿った来歴・署名・失効情報と、利用例・制約の説明を公開元で用意する。
- 利用するHostが、その正確なFormRefを実装し、対象の利用者に許可しているかを確認する。
署名の検証に成功することと、その公開元を信頼するかは別です。信頼ポリシーは利用者・運用者が 決めます。Coreへの中央カタログ登録で利用可能になる仕組みではありません。
次は 共通モデル でSnapshotへの参照のまとめ方を確認するか、 GoからHostを使う でAPIの呼び出しを試してください。