{ 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; }; }