Мы используем Terraformer
для получения состояния и HCL
файлов, которые затем используем в модулях Terraform
для управления нашей инфраструктурой и предотвращения дрейфа. Поскольку файлы tfvars
передаются в модуль как переменные, нам нужен вывод tfvars
для автоматизации всего процесса. Вот простая иллюстрация:
infrastructure -> terraformer -> (HCl, statefile) -> tfvars -> terraform module -> infrastructure
Поскольку Terraformer
не поддерживает вывод tfvars
, я пытаюсь написать модуль Go для преобразования HCL
файлов (или statefile
) в .tfvars
файлы. Поискав в Интернете, я наткнулся на этот вопрос и решение о переполнении стека; однако речь шла о разборе файла tfvars
. В принятом ответе предлагалось использовать модуль cty
в качестве парсера низкого уровня.
Учитывая это, я попробовал следующее:
// ...
parser := hclparse.NewParser()
// filePath point to a hclfile in JOSON format
hclFile, diags := parser.ParseJSONFile(filePath)
attrs, diags := hclFile.Body.JustAttributes()
if diags.HasErrors() {
log.Fatal(diags.Error())
}
vals := make(map[string]cty.Value, len(attrs))
for name, attr := range attrs {
vals[name], diags = attr.Expr.Value(nil)
if diags.HasErrors() {
log.Fatal(diags.Error())
}
}
До сих пор мне удавалось разобрать HCL на типы cty. Однако, поскольку cty не поддерживает сериализацию в файл tfvars, я попытался написать собственный сериализатор. Это правильный обходной путь? Как я мог достичь этой цели?
Вот сериализатор:
func serializeValue(value cty.Value) string {
switch {
case value.Type().IsPrimitiveType():
switch value.Type() {
case cty.String:
return fmt.Sprintf("\"%s\"", value.AsString())
case cty.Number:
return value.AsBigFloat().Text('f', -1)
case cty.Bool:
return fmt.Sprintf("%t", value.True())
}
case value.Type().IsListType() || value.Type().IsTupleType():
return serializeList(value)
case value.Type().IsMapType() || value.Type().IsObjectType():
return serializeMapOrObject(value)
default:
panic("Unhandled type")
}
return ""
}
func serializeMapOrObject(value cty.Value) string {
var elements []string
for key, val := range value.AsValueMap() {
elements = append(elements, fmt.Sprintf("\"%s\" = %s\n", key, serializeValue(val)))
}
return fmt.Sprintf("{%s}", strings.Join(elements, "\n"))
}
func serializeList(value cty.Value) string {
var elements []string
for _, elem := range value.AsValueSlice() {
elements = append(elements, serializeValue(elem))
}
return fmt.Sprintf("[%s]", strings.Join(elements, ", "))
}
Если у вас есть cty.Value
, который, как вы знаете, относится к типу объекта или карты и что все имена атрибутов/ключей являются действительными идентификаторами HCL, вы можете использовать пакет hclwrite HCL, чтобы сгенерировать что-то, что Terraform примет в качестве контента .tfvars
файл.
Формат файла .tfvars
Terraform — это относительно простое применение HCL, где корневое тело интерпретируется как «просто атрибуты» (в терминологии HCL), а затем для каждого атрибута оценивается выражение без доступных переменных или функций.
Вы можете сгенерировать такой файл следующим образом, с подходящим cty.Value
сохраненным в переменной obj
:
f := hclwrite.NewEmptyFile()
body := f.Body()
for it := obj.ElementIterator(); it.Next(); {
k, v := it.Element()
name := k.AsString()
body.SetAttributeValue(name, v)
}
result := f.Bytes()
После выполнения вышеизложенного result
становится []byte
, содержащим контент, который вы можете записать в свой .tfvars
файл.
cty
не выполняет итерацию элементов карты или атрибутов объекта в каком-либо предсказуемом порядке, поэтому атрибуты в результате также будут находиться в непредсказуемом порядке. Если вы хотите гарантировать порядок, вам нужно будет сделать что-то более сложное, чем это, но я оставлю это в качестве упражнения, поскольку выше показан базовый механизм создания файла HCL, содержащего только атрибуты, значения которых константы.
Terraform принимает два разных формата для предоставления входных переменных в корневой модуль:
.tfvars
основан на синтаксисе HCL.tfvars.json
основан на синтаксисе JSONЕсли ваши определения входных переменных всегда генерируются машиной (никогда не поддерживаются людьми вручную), то вместо этого обычно проще сгенерировать формат .tfvars.json
, поскольку большинство языков программирования имеют подходящую существующую библиотеку для преобразования значений в сопоставимые значения JSON в синтаксисе JSON.
Для файла .tfvars.json
Terraform ожидает, что файл будет состоять из объекта JSON, имена свойств которого соответствуют именам входных переменных вашего корневого модуля. Значения, присвоенные этим свойствам, преобразуются в значения Terraform с использованием таблицы перевода, аналогичной функции jsondecode, а затем эти значения преобразуются в типы, объявленные для каждой переменной, с использованием аргумента type
в каждом блоке variable
.
Этот подход означает, что вашему генератору вообще не нужно знать синтаксис HCL, и вместо этого он может просто генерировать JSON.
Если это сработает, кого это волнует ;)