Fediversity/deployment/tf-conversion.nix
Kiara Grouwstra d7dbe144ae
add conversion from TF
Signed-off-by: Kiara Grouwstra <kiara@procolix.eu>
2025-11-10 23:18:52 +01:00

254 lines
5.8 KiB
Nix

{
lib,
pkgs,
...
}:
let
inherit (pkgs.callPackage ./utils.nix { }) cast;
inherit (lib)
filterAttrs
flatten
mapAttrs
mkOption
removeAttrs
throwIf
types
;
inherit (types)
attrsOf
bool
enum
float
ints
number
submodule
listOf
str
;
sourceSchemas = filterAttrs (
k: _:
lib.elem k [
"data_source_schemas"
"resource_schemas"
]
);
in
rec {
# helpers to obtain TF provider data into `tfvars.json` to easily wrap TF resources / data sources
wrapTfType =
tfType: lib.foldr (typ: acc: if acc == null then typ else "${typ}(${acc})") null (flatten tfType);
wrapTfAttr =
{ type, ... }@attr:
removeAttrs attr [
"description_kind"
"computed"
"required"
"optional"
]
// {
type = wrapTfType type;
}
// (if (attr ? optional) then { default = null; } else { });
wrapTfAttrs = tfAttrs: {
variable = lib.mapAttrs (_: wrapTfAttr) (filterAttrs (_: v: !(v ? computed)) tfAttrs);
};
wrapTfSourceSchemas = mapAttrs (_: schemas: wrapTfAttrs schemas.block.attributes);
wrapTfProvider = schema: mapAttrs (_: wrapTfSourceSchemas) (sourceSchemas schema);
wrapTfProviderSchema =
output:
lib.mapAttrs' (k: v: {
name = lib.removePrefix "registry.opentofu.org/" k;
value = wrapTfProvider v;
}) output.provider_schemas;
# converting types
tfAttrType = submodule {
options = {
"type" = mkOption {
type = types.either str (types.listOf str);
};
"description" = mkOption {
type = str;
# default = "";
};
"description_kind" = mkOption {
type = enum [ "plain" ];
default = "plain";
};
"computed" = mkOption {
type = bool;
default = false;
};
"sensitive" = mkOption {
type = bool;
default = false;
};
"required" = mkOption {
type = bool;
default = false;
};
"optional" = mkOption {
type = bool;
default = false;
};
"deprecated" = mkOption {
type = bool;
default = false;
};
};
};
tfAttrsType = attrsOf tfAttrType;
# converting TF to nix modules
fromTfTypes =
types:
let
typ = (lib.head types);
typs = (lib.tail types);
rest = (fromTfTypes typs);
in
{
inherit bool number;
"string" = str;
"int32" = ints.s32;
"int64" = ints.s32;
"float32" = float;
"float64" = float;
"dynamic" = types.unspecified;
"list" = listOf rest;
"map" = attrsOf rest;
"set" = listOf rest; # no unordered type in nix
object = throw "to be implemented";
tuple = throw "to be implemented";
}
.${typ};
fromTfAttr =
tfAttr:
let
inherit (cast tfAttrType tfAttr)
type
description
computed
optional
required
;
types = flatten type;
in
throwIf computed "computed TF attributes cannot be translated to Nix" mkOption (
(
if optional then
{ default = null; }
else
throwIf (!required) "either of required or optional must be true" { }
)
// {
inherit description;
type = fromTfTypes types;
}
);
fromTfAttrs =
allowSensitive: tfAttrs:
submodule {
options = lib.mapAttrs (_: fromTfAttr) (
lib.filterAttrs (
_: v: (if allowSensitive then true else !(v ? sensitive)) && !(v ? computed) && !(v ? deprecated)
) tfAttrs
);
};
fromTfSourceSchemas =
allowSensitive: schemas:
submodule {
options = mapAttrs (
_: { block, ... }: mkOption { type = fromTfAttrs allowSensitive block.attributes; }
) schemas;
};
fromTfProvider =
allowSensitive: schema:
submodule {
options = mapAttrs (_: schemas: mkOption { type = fromTfSourceSchemas allowSensitive schemas; }) (
sourceSchemas schema
);
};
fromTfProviderSchema =
allowSensitive:
{ provider_schemas, ... }:
submodule {
options = lib.mapAttrs' (k: schema: {
name = lib.removePrefix "registry.opentofu.org/" k;
value = mkOption { type = fromTfProvider allowSensitive schema; };
}) provider_schemas;
};
# extract from TF data
extractProviderSchemas =
allowSensitive: pluginFn:
let
tf = (pkgs.callPackage ./tf.nix { }).withPlugins pluginFn;
usedPlugins = pluginFn tf.plugins;
mainTf = pkgs.writers.writeJSON "main.tf.json" {
terraform.required_providers = lib.listToAttrs (
lib.lists.map (
{
meta,
owner,
version,
...
}:
let
name = lib.last (lib.splitString "/" meta.homepage);
in
{
inherit name;
value = {
source = "${owner}/${name}";
version = "= ${version}";
};
}
) usedPlugins
);
};
schemas = pkgs.stdenv.mkDerivation {
name = "tf-extract";
src = pkgs.linkFarm "tf-providers-main" [
{
name = "main.tf.json";
path = mainTf;
}
];
buildInputs = [
tf
pkgs.jq
];
buildPhase = ''
tofu init 1>/dev/null
tofu providers schema -json | jq . > ./schemas.json
'';
installPhase = ''
mkdir -p $out/share
cp ./schemas.json $out/share
'';
};
schema = lib.importJSON (builtins.storePath (builtins.toPath "${schemas}/share/schemas.json"));
wrapped = wrapTfProviderSchema schema;
converted = fromTfProviderSchema allowSensitive schema;
in
{
inherit schema wrapped converted;
};
}