Skip to content

Typed Resource Options#

Kubenix has done a great work with generating nix options definitions from official JSON schemas and nixidy builds on top of this.

All core Kubernetes resources are imported by default in nixidy along with Argo CD's Application and AppProject. Every resource can be defined under applications.<applicationName>.resources.<group>.<version>.<kind> but is also offered as an alias applications.<applicationName>.resources.<attrName> where <attrName> is the plural form of the kind in camelCase.

For example:

  • resources.core.v1.Service -> resources.services
  • resources."networking.k8s.io".v1.NetworkPolicy -> resources.networkPolicies

The lack of typed resource options only prevents defining resources in nix. Manifests rendered from a Helm Chart or defined in applications.<applicationName>.yamls that do not have resource options for that group, version and kind go straight to the application output and cannot be patched by nixidy.

Including arbitrary YAML#

To include YAML files in an application's output without nixidy parsing them, use applications.<applicationName>.extraRawYamls. The listed files are copied into the application's output directory and are never round-tripped through kube.fromYAML, so they are not available under resources and cannot be patched in nix.

A common use case is SOPS-encrypted manifests: the top-level sops metadata block would otherwise be stripped, and ENC[...] ciphertext values would be re-formatted in ways that break decryption.

applications.my-app = {
  namespace = "my-app";

  extraRawYamls = [ ./encrypted-secret.yaml ];
};

Each file's basename becomes the output filename. Basenames must be unique within an application and must not collide with typed-resource output filenames (e.g. Secret-<name>.yaml); both cases produce build-time assertion failures.

Warning

extraRawYamls is only included in the ArgoCD-targeted environment output. The kubectl apply --prune flow produced by direct_apply only handles typed objects and skips raw files. SOPS-encrypted manifests aren't valid Kubernetes objects until decrypted, so they are expected to be applied by ArgoCD (with a SOPS plugin) rather than kubectl apply.

Generating your own resource options from CRDs#

nixidy provides a code generator for generating resource options from CRDs (this is based heavily on kubenix's code generator).

A matrix of CRD accessors are available. Pick one by what you want back (a generated .nix file, a live module value, or the raw CRD manifests) and where the CRDs come from (local source files, or a Helm chart):

source files (src) Helm chart
→ generated file fromCRD fromChartCRD
→ module value fromCRDModule fromChartCRDModule
→ raw objects crdObjects crdObjectsFromChart
  • → generated file renders the resource options to Nix source, formats it, and writes a .nix file you commit and import. Use this when you want the generated types checked into your repository and reviewable in diffs.
  • → module value returns the resource options as a module value directly, with no generated file and no import. Pass the result directly to nixidy.applicationImports. This is useful for flake or programmatic consumers that do not need a committed file.
  • → raw objects returns the CustomResourceDefinition manifests themselves as Nix values (not resource options), for example to deploy the CRDs alongside the resources that use them.

All variants share the same name, namePrefix, attrNameOverrides and skipCoerceToList arguments; the source-based ones take crdFiles and the chart-based ones take chartAttrs/chart/values/kubeVersion. Both families take an optional kindFilter to narrow which CRD kinds are processed.

From source files (fromCRD)#

As an example, to generate resource options for Cilium's CiliumNetworkPolicy and CiliumClusterwideNetworkPolicy the following can be defined in flake.nix.

flake.nix
{
  description = "My ArgoCD configuration with nixidy.";
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.nixidy = {
    url = "github:arnarg/nixidy/latest";
    inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs = {
    self,
    nixpkgs,
    flake-utils,
    nixidy,
  }: (flake-utils.lib.eachDefaultSystem (system: let
    pkgs = import nixpkgs {
      inherit system;
    };
  in {
    packages = {
      generators.cilium = nixidy.packages.${system}.generators.fromCRD {
        name = "cilium";
        src = pkgs.fetchFromGitHub {
          owner = "cilium";
          repo = "cilium";
          rev = "v1.15.6";
          hash = "sha256-oC6pjtiS8HvqzzRQsE+2bm6JP7Y3cbupXxCKSvP6/kU=";
        };
        crdFiles = [
          "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnetworkpolicies.yaml"
          "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumclusterwidenetworkpolicies.yaml"
        ];
      };
    };
  }));
}

Then running nix build .#generators.cilium will produce a nix file that can be copied into place in your repository. After that the generated file has to be added to nixidy.applicationImports in your nixidy modules.

env/dev.nix
{
  nixidy.applicationImports = [
    ./generated/cilium.nix
  ];
}
Using nixidy without flakes

If you prefer not to use flakes, you can write the following in generate.nix.

generate.nix
let
  # With npins
  sources = import ./npins;
  # With niv
  # sources = import ./nix/sources.nix;

  # nixpkgs added with:
  #   npins: `npins add --name nixpkgs channel nixos-unstable`
  #   niv: `niv add github nixos/nixpkgs -b nixos-unstable`
  nixpkgs = sources.nixpkgs;
  pkgs = import nixpkgs {};

  # Import nixidy
  nixidy = import sources.nixidy {inherit nixpkgs;};
in
  {
    cilium = nixidy.generators.fromCRD {
      name = "cilium";
      src = pkgs.fetchFromGitHub {
        owner = "cilium";
        repo = "cilium";
        rev = "v1.15.6";
        hash = "sha256-oC6pjtiS8HvqzzRQsE+2bm6JP7Y3cbupXxCKSvP6/kU=";
      };
      crdFiles = [
        "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnetworkpolicies.yaml"
        "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumclusterwidenetworkpolicies.yaml"
      ];
    };
  }

Then running nix-build generate.nix -A cilium will produce a nix file that can be copied into place in your repository and imported using nixidy.applicationImports in a nixidy module.

From Helm Chart CRDs (fromChartCRD)#

In some cases, CRDs are only available through Helm charts or it's beneficial to keep them in sync with the chart version you're deploying. The fromChartCRD function provides a solution by templating the Helm chart and extracting CRDs from the output, generating nixidy resource options from them.

This approach also handles CRDs that include Helm templating within their definitions, which would not be properly processed by fromCRD.

As an example, to generate resource options for cert-manager's Certificate CRD directly from the Helm chart:

flake.nix
{
  description = "My ArgoCD configuration with nixidy.";
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.nixidy = {
    url = "github:arnarg/nixidy/latest";
    inputs.nixpkgs.follows = "nixpkgs";
  };
  inputs.nixhelm = {
    url = "github:farcaller/nixhelm";
    inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs = {
    self,
    nixpkgs,
    flake-utils,
    nixidy,
    nixhelm,
  }: (flake-utils.lib.eachDefaultSystem (system: let
    pkgs = import nixpkgs {
      inherit system;
    };
  in {
    packages = {
      generators.certManager = nixidy.packages.${system}.generators.fromChartCRD {
        name = "cert-manager";
        chartAttrs = {
          repo = "https://charts.jetstack.io";
          chart = "cert-manager";
          version = "v1.19.1";
          chartHash = "sha256-fs14wuKK+blC0l+pRfa//oBV2X+Dr3nNX+Z94nrQVrA=";
        };
        # Or from nixhelm
        # chart = nixhelm.chartsDerivations.${system}.jetstack.cert-manager;
        kindFilter = [ "Certificate" ];  # Optional: filter by specific CRD kinds
        extraOpts = [ "--set", "crds.enabled=true"]; # Optional: pass extra options to helm template generation
      };
    };
  }));
}

Then running nix build .#generators.certManager will produce a nix file that can be copied into place in your repository.

Using nixidy without flakes

If you prefer not to use flakes, you can write the following in generate.nix.

generate.nix
let
  # With npins
  sources = import ./npins;
  # With niv
  # sources = import ./nix/sources.nix;

  # nixpkgs added with:
  #   npins: `npins add --name nixpkgs channel nixos-unstable`
  #   niv: `niv add github nixos/nixpkgs -b nixos-unstable`
  nixpkgs = sources.nixpkgs;
  pkgs = import nixpkgs {};

  # Import nixidy
  nixidy = import sources.nixidy {inherit nixpkgs;};
in
  {
    certManager = nixidy.generators.fromChartCRD {
      name = "cert-manager";
      chartAttrs = {
        repo = "https://charts.jetstack.io";
        chart = "cert-manager";
        version = "v1.19.1";
        chartHash = "sha256-fs14wuKK+blC0l+pRfa//oBV2X+Dr3nNX+Z94nrQVrA=";
      };
      kindFilter = [ "Certificate" ];  # Optional: filter by specific CRD kinds
      extraOpts = [ "--set", "crds.enabled=true"]; # Optional: pass extra options to helm template generation
    };
  }

Then running nix-build generate.nix -A certManager will produce a nix file that can be copied into place in your repository and imported using nixidy.applicationImports in a nixidy module.

The fromChartCRD function accepts the same optional arguments as fromCRD (namePrefix, attrNameOverrides, and skipCoerceToList) for customization of the generated options. Additionally, it accepts:

  • chartAttrs: Chart repository, name, version and chartHash configuration
  • chart: Alternative to chartAttrs, can use a pre-downloaded chart
  • values: Values to pass to the Helm chart templating
  • kindFilter: List of CRD kinds to extract (empty list extracts all CRDs)
  • kubeVersion: Kubernetes version to template the chart against (helm template --kube-version). Defaults to the version in nixidy's nixpkgs; set it to match the cluster the CRDs are destined for.
  • extraOpts: List of extra arguments to pass through to helm template

Renamed argument

kindFilter was previously called crds. The old name still works as a deprecated alias (it emits a warning pointing at kindFilter); prefer kindFilter in new code. Note that on fromCRD the crds argument instead became crdFiles (the two used to share a name despite meaning different things: a kind filter vs. a list of YAML files).

As a module value (fromCRDModule / fromChartCRDModule)#

fromCRD and fromChartCRD build a .nix file that you commit and import. For flake or other programmatic consumers that do not need a committed file, fromCRDModule (and its chart counterpart fromChartCRDModule) return the resource options as a module value instead, skipping the render-to-source, format, write-file and import round-trip. The result is a module function, which is exactly what nixidy.applicationImports accepts.

modules/cilium.nix
{ generators, ... }: {
  nixidy.applicationImports = [
    (generators.fromCRDModule {
      name = "cilium";
      src = pkgs.fetchFromGitHub {
        owner = "cilium";
        repo = "cilium";
        rev = "v1.15.6";
        hash = "sha256-oC6pjtiS8HvqzzRQsE+2bm6JP7Y3cbupXxCKSvP6/kU=";
      };
      crdFiles = [
        "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnetworkpolicies.yaml"
        "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumclusterwidenetworkpolicies.yaml"
      ];
    })
  ];
}

fromChartCRDModule takes the same chart arguments as fromChartCRD (chartAttrs/chart/values/kindFilter/kubeVersion/extraOpts). Because no file is generated there is nothing to nix build or commit; the types are produced during evaluation.

Raw CRD objects (crdObjects / crdObjectsFromChart)#

Sometimes you do not want resource options at all but rather the CustomResourceDefinition manifests themselves, for example to deploy the CRDs into the cluster alongside the resources that use them. crdObjects (source files) and crdObjectsFromChart (Helm chart) return the CRD manifests as a list of plain Nix attribute sets.

One way to deploy them is to feed them into an application's yamls, which parses YAML/JSON strings into the application's output (builtins.toJSON output is valid YAML):

# inside an env module
applications.crds = {
  namespace = "kube-system";
  yamls = map builtins.toJSON (
    nixidy.packages.${system}.generators.crdObjects {
      src = ciliumSrc; # the pkgs.fetchFromGitHub from the fromCRD example
      crdFiles = [
        "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnetworkpolicies.yaml"
        "pkg/k8s/apis/cilium.io/client/crds/v2/ciliumclusterwidenetworkpolicies.yaml"
      ];
    }
  );
};

crdObjectsFromChart takes the same chart arguments as fromChartCRD. Both accept an optional kindFilter to keep only specific CRD kinds (an empty or unset list keeps all of them).

Resolving Naming Conflicts#

Sometimes, multiple Custom Resource Definitions from different sources might define the same resource kind. This can lead to conflicts in the generated attribute names. For instance, if two different operators both define a CRD with the kind Database, they would both try to generate options for resources.databases.

To resolve this, the fromCRD function accepts a namePrefix argument. This prefix will be added to the generated attribute name, making it unique.

For example, if you have two operators that both provide a Database CRD, you can distinguish them like this:

{
  # For postgres-operator
  postgres = nixidy.generators.fromCRD {
    name = "postgres-operator";
    namePrefix = "postgres";
    # ...
  };

  # For mysql-operator
  mysql = nixidy.generators.fromCRD {
    name = "mysql-operator";
    namePrefix = "mysql";
    # ...
  };
}

This will generate resources.postgresDatabases and resources.mysqlDatabases respectively, avoiding any conflicts.

If the heuristics for attribute name generation still create conflicts, for example within the same chart, or if you wish to further customize the name for ergonomics, fromCRD also accepts an attrNameOverrides argument which takes precedence over all other methods.

This argument is a mapping from the CRD's name (<plural>.<group>) to the desired attribute name. For example:

{
  keycloak = nixidy.generators.fromCRD {
    name = "keycloak";
    src = pkgs.fetchFromGitHub {
      owner = "crossplane-contrib";
      repo = "provider-keycloak";
      ...
    };
    crdFiles = [
      # This CRD conflicts with Kubernetes builtin Binding
      "package/crds/authenticationflow.keycloak.crossplane.io_bindings.yaml"
      # These CRDs have identical plural references
      "package/crds/group.keycloak.crossplane.io_groups.yaml"
      "package/crds/user.keycloak.crossplane.io_groups.yaml"
    ];
    namePrefix = "keycloak";
    attrNameOverrides."groups.user.keycloak.crossplane.io" = "keycloakUserGroups";
  };
}

will expose keycloakBindings, keycloakGroups, and keycloakUserGroups under an application's resources.