TT Lab
はじめる
学ぶ 学習パス コース

Terraform/OpenTofu基礎

値を手でつなげて引用符に負けた

TT Labで続きを見る

目標

文字列・コレクション・条件・変換・テンプレート・シリアライズの関数を、コンソールで1行ずつ確認したあと、そのまま設定に移して、実際のファイルを作ります。最後に、値を1つも直接書かず、変数からだけ引いてくる設定を作って、別の入力にも耐えられるかを確認します。

なぜ重要なのか

設定ファイルが長くなる理由は、たいてい同じ値を複数の場所に書くからです。名前を組み合わせる規則、環境ごとに違う値、マップを合成するときの優先順位のようなものを、関数で書いておけば、値を1つ変えるときに直す箇所が1か所に減ります。そして、関数はコンソールで1行で確認できるので、式が紛らわしいときに適用を繰り返すコストがなくなります。失敗を扱う関数も重要です。外から入ってきたデータは、いつでも壊れる可能性があるのに、そのたびに適用全体が止まると、運用が他人のミスに縛られます。失敗を値に変える関数を知っていれば、デフォルト値に落ち着く設計ができます。最後に、シリアライズの関数は、引用符とエスケープを、人が責任を持たなくて済むようにしてくれます。手でつないだJSONは、いつか必ず壊れます。

ステップ

  1. /root/tfb-func/playを作成して、localプロバイダーだけを宣言したmain.tfでinitしてください。/root/tfb-func/strings-in.txtに6行を書いてください。join("-", ["web", "prod", "01"])、format("%s-%03d", "node", 7)、replace("db-prod-01", "prod", "stage")、trimspace(" padded ")、substr("abcdefgh", 2, 3)、upper(join("_", sort(["b", "a"])))です。そのファイルをtofu consoleに流し込んで、結果を/root/tfb-func/strings-out.txtに保存してください。
  2. /root/tfb-func/coll/main.tfに、map(number)の変数ports(web 80、api 8080)と、mergeで2つのタグのマップを合成したlocal.tags、sort(keys(...))で作ったlocal.names、lookupで読み取ったlocal.api(あるキー)とlocal.db(ないキー、デフォルト値5432)、distinctで重複をなくしたlocal.uniqueを置いて、5つの出力として出力してください。init・applyしたあと、tofu output -jsonを/root/tfb-func/collections.jsonに保存してください。
  3. /root/tfb-func/cond/main.tfに、デフォルト値がprodの変数envと、var.env == "prod"を入れたlocal.is_prod、それで選ぶlocal.replicas(3または1)、local.log_level(warnまたはdebug)、local.backup(dailyまたはnone)、formatで作ったlocal.endpoint(ポートは443または8080)を置いて、4つの値を키=값の4行で書くapp.confを作成してください(プレースホルダーは、順にキーと値です)。init・applyしたあと、tofu console -var env=devで同じ4つの値を評価して、/root/tfb-func/cond-dev.txtに保存してください。
  4. /root/tfb-func/guard/に、正しいJSONのgood.json(nameがapi、portが8080)と、壊れたJSONのbad.jsonを作成してください。main.tfで、2つのファイルをtry(jsondecode(...), {})で読み、あるキーとないキーを、それぞれtry(..., "unknown")で取り出し、can(jsondecode(...))で2つのファイルの成否を入れて、5つの出力(name・miss・bad_keys・ok_good・ok_bad)として出力してください。applyしたあと、tofu output -jsonを/root/tfb-func/guard.jsonに保存してください。
  5. /root/tfb-func/tpl/site.tftplに、名前の1行、ポートのリストを回す繰り返し、TLSかどうかによる条件を含んだテンプレートを書いて、main.tfで、templatefileでname = "api"、ports = [80, 443]、tls = trueを渡して、site.confを作成してください。結果は、server {で始まり、name = api、listen 80;、listen 443;、tls on;、}の6行でなければなりません。
  6. /root/tfb-func/enc/main.tfに、local.data(name = api、ports = [80, 443]、tls = true)を置いて、jsonencodeでdata.json、yamlencodeでdata.yamlを作成してください。applyしたあと、jq -c .で読んだ結果を/root/tfb-func/enc-json.txtに、yq -o=json -I=0 .で読んだ結果を/root/tfb-func/enc-yaml.txtに保存してください。2つのファイルの内容は、同じである必要があります。
  7. /root/tfb-func/convert-in.txtに6行を書いてください。parseint("ff", 16)、tonumber("007")、tostring(true)、try(tonumber("abc"), -1)、can(tonumber("abc"))、format("%s has %d ports", "api", length([80, 443]))です。/root/tfb-func/playで、コンソールに流し込んで、結果を/root/tfb-func/convert-out.txtに保存してください。
  8. /root/tfb-func/compose/main.tfに、list(string)の変数names(デフォルトapi、web、db)と、list(number)の変数ports(デフォルト8080、80、5432)を置いて、formatlistで이름=포트の行のリストを作り、joinでつなぎ、formatとlengthで# <개수> servicesという見出しを作って、services.confを作成してください(プレースホルダーは、順に名前とポート、件数です)。1行目は見出しで、その後ろがサービスの行です。値を直接書かず、変数からだけ引いてきてください。採点ツールが、別の入力で同じ設定を適用してみます。

参考

文字列関数をコンソールで確認する

/root/tfb-func/playを作成して、localプロバイダーだけを宣言したmain.tfでinitしてください。/root/tfb-func/strings-in.txtに6行を書いてください。join("-", ["web", "prod", "01"])、format("%s-%03d", "node", 7)、replace("db-prod-01", "prod", "stage")、trimspace(" padded ")、substr("abcdefgh", 2, 3)、upper(join("_", sort(["b", "a"])))です。そのファイルをtofu consoleに流し込んで、結果を/root/tfb-func/strings-out.txtに保存してください。

コンソールは、ファイルを流し込むと、1行ずつ評価して、結果だけを出力します。formatのプレースホルダーは、書式文字列の規則にそのまま従い、substrの2つ目の引数は、長さではなく開始位置です。

マップとリストを合成して、取り出す

/root/tfb-func/coll/main.tfに、map(number)の変数ports(web 80、api 8080)と、mergeで2つのタグのマップを合成したlocal.tags、sort(keys(...))で作ったlocal.names、lookupで読み取ったlocal.api(あるキー)とlocal.db(ないキー、デフォルト値5432)、distinctで重複をなくしたlocal.uniqueを置いて、5つの出力として出力してください。init・applyしたあと、tofu output -jsonを/root/tfb-func/collections.jsonに保存してください。

mergeは、後ろに来たマップが勝ちます。lookupの3つ目の引数は、キーがないときのデフォルト値で、これを抜かすと、ないキーでエラーになります。keysは、並び順を保証しないので、sortで包みます。

条件式で環境ごとの値を選ぶ

/root/tfb-func/cond/main.tfに、デフォルト値がprodの変数envと、var.env == "prod"を入れたlocal.is_prod、それで選ぶlocal.replicas(3または1)、local.log_level(warnまたはdebug)、local.backup(dailyまたはnone)、formatで作ったlocal.endpoint(ポートは443または8080)を置いて、4つの値を키=값の4行で書くapp.confを作成してください(プレースホルダーは、順にキーと値です)。init・applyしたあと、tofu console -var env=devで同じ4つの値を評価して、/root/tfb-func/cond-dev.txtに保存してください。

条件式は、真のときと偽のときの型が同じである必要があります。コンソールに変数の値を与えると、設定を直さなくても、別の分岐の結果を見られるので、適用を繰り返さなくても確認が終わります。

壊れた入力を値として吸収する

/root/tfb-func/guard/に、正しいJSONのgood.json(nameがapi、portが8080)と、壊れたJSONのbad.jsonを作成してください。main.tfで、2つのファイルをtry(jsondecode(...), {})で読み、あるキーとないキーを、それぞれtry(..., "unknown")で取り出し、can(jsondecode(...))で2つのファイルの成否を入れて、5つの出力(name・miss・bad_keys・ok_good・ok_bad)として出力してください。applyしたあと、tofu output -jsonを/root/tfb-func/guard.jsonに保存してください。

tryは、前の引数の評価が失敗したら、次の引数を返し、canは、成功したかどうかを真偽に変えてくれます。どちらも、実行中に生じるエラーだけを捕まえます。宣言すらない参照のような、静的なエラーは捕まえられません。

テンプレートで設定ファイルを出力する

/root/tfb-func/tpl/site.tftplに、名前の1行、ポートのリストを回す繰り返し、TLSかどうかによる条件を含んだテンプレートを書いて、main.tfで、templatefileでname = "api"、ports = [80, 443]、tls = trueを渡して、site.confを作成してください。結果は、server {で始まり、name = api、listen 80;、listen 443;、tls on;、}の6行でなければなりません。

テンプレートの繰り返しと条件は、パーセント記号で始まるディレクティブです。ディレクティブの行が、結果に空行として残らないようにするには、閉じる波括弧の前に、空白除去マーカーを付けます。テンプレートファイルの拡張子は自由ですが、慣例があります。

同じデータを2つの形式で出力する

/root/tfb-func/enc/main.tfに、local.data(name = api、ports = [80, 443]、tls = true)を置いて、jsonencodeでdata.json、yamlencodeでdata.yamlを作成してください。applyしたあと、jq -c .で読んだ結果を/root/tfb-func/enc-json.txtに、yq -o=json -I=0 .で読んだ結果を/root/tfb-func/enc-yaml.txtに保存してください。2つのファイルの内容は、同じである必要があります。

2つの関数は、同じ値を別の表記に移すだけなので、読み直すと同じデータが出てきます。設定ファイルを手でつなぐ代わりに、これらの関数を使えば、引用符とエスケープを、ツールが責任を持ちます。

変換関数とその失敗を確認する

/root/tfb-func/convert-in.txtに6行を書いてください。parseint("ff", 16)、tonumber("007")、tostring(true)、try(tonumber("abc"), -1)、can(tonumber("abc"))、format("%s has %d ports", "api", length([80, 443]))です。/root/tfb-func/playで、コンソールに流し込んで、結果を/root/tfb-func/convert-out.txtに保存してください。

変換関数は、変換できない値に出会うと、エラーを出します。そのエラーを値に変えるのがtryで、真偽に変えるのがcanです。進法が10ではない文字列には、専用の関数が別にあります。

関数を組み合わせて、設定ファイル1つにまとめる

/root/tfb-func/compose/main.tfに、list(string)の変数names(デフォルトapi、web、db)と、list(number)の変数ports(デフォルト8080、80、5432)を置いて、formatlistで이름=포트の行のリストを作り、joinでつなぎ、formatとlengthで# <개수> servicesという見出しを作って、services.confを作成してください(プレースホルダーは、順に名前とポート、件数です)。1行目は見出しで、その後ろがサービスの行です。値を直接書かず、変数からだけ引いてきてください。採点ツールが、別の入力で同じ設定を適用してみます。

formatlistは、複数のリストを並べて回しながら、同じ書式を適用します。見出しの個数は、数えて入れてはじめて、入力が変わっても合います。ここまでくれば、値を1つ変えるために、ファイルを複数箇所直すことがなくなります。