nixidy

repository·main·Indexed 18 days ago

https://github.com/arnarg/nixidy

A Kubernetes GitOps tool that uses Nix and the NixOS module system to define cluster state declaratively. It provides strong typing and composability to generate plain YAML manifests for tools like Argo CD. Features include Helm chart and Kustomize integration, multi-environment configuration management, reusable application templates, and the ability to generate typed Nix options from CRDs. Includes a CLI for building, applying, and bootstrapping configurations.

Tokens
24.5K
Snippets
78
Records
98
Agent score
62%

What's inside nixidy

  1. Understand the Nixidy Project Structure

    main

    Nixidy is organized into several key directories that separate core logic, configuration modules, and tooling:

    • cli/: Python-based CLI tool.
    • lib/: Nix function library containing helpers for Helm, Kubernetes, and Kustomize.
    • modules/: The core NixOS-style modules, including:
      • applications/: Submodule for defining applications, Helm/Kustomize processing, and the resource type registry.
      • generated/: Auto-generated resource options for ArgoCD and Kubernetes.
      • nixidy/: Core configuration (environment, targets, transforms).
      • build/: Logic for rendering and applying output packages.
    • pkgs/generators/: Tools for compiling Kubernetes CRDs and OpenAPI schemas into Nix options.
    • tests/: Unit tests for Helm and Kustomize logic.
    • flake.nix / default.nix: Entry points for the Nix flake and non-flake environments.
  2. Configure nixidy target and applications

    main

    Nixidy configuration is defined in environment modules (e.g., env/dev.nix). You define where manifests should be stored and what applications to build.

    Target Configuration

    Use nixidy.target to specify the Git repository, branch, and the root path where generated manifests will be stored.

    • nixidy.target.repository: The Git URL for your manifests.
    • nixidy.target.branch: The target branch (e.g., main).
    • nixidy.target.rootPath: The directory within the repository where manifests are written.

    Application Configuration

    Use applications.<name> to define a Kubernetes application.

    • namespace: The Kubernetes namespace for the application.
    • createNamespace: Boolean to automatically create the namespace.
    • resources: A set of Kubernetes resources (e.g., deployments, services, configMaps, statefulSets) defined using their spec structure.
    {
      # Target configuration
      nixidy.target.repository = "https://github.com/YOUR_USERNAME/my-cluster.git";
      nixidy.target.branch = "main";
      nixidy.target.rootPath = "./manifests/dev";
    
      # Define an application
      applications.nginx = {
        namespace = "nginx";
        createNamespace = true;
    
        resources = {
          deployments.nginx.spec = {
            replicas = 2;
            # ... rest of spec
          };
          services.nginx.spec = {
            # ... rest of spec
          };
        };
      };
    }
  3. Define an Object Transform rule

    main

    A rule consists of a match criteria and exactly one of two actions: rewrite or postProcess.

    • name: (Optional) A string used in assertion messages and logs.
    • match: Defines which objects the rule applies to. If omitted, it matches every object.
    • rewrite: An evaluation-time function (resource -> resource) used to modify the object structure.
    • postProcess: An activation-time filter (stdin -> stdout) used to run external commands on the rendered manifest content.

    Note: A rule's match predicate runs against the object as it exists at that point in the pipeline. If a previous rule renamed a kind, subsequent rules must match the new kind.

    {
      name = "encrypt-secrets";
      match.kind = "Secret";
    
      # exactly one of the following:
      rewrite = resource: resource;
      # OR
      postProcess = "<command>";
    }
  4. Access Kubernetes resources via typed aliases

    main

    Nixidy provides strongly-typed options for Kubernetes resources. While you can define resources using their full GVK (Group, Version, Kind) path under applications.<applicationName>.resources.<group>.<version>.<kind>, Nixidy also provides convenient camelCase plural aliases for common resources.

    Path Formats:

    • Full Path: applications.<applicationName>.resources.<group>.<version>.<kind>
    • Alias Path: applications.<applicationName>.resources.<attrName> (where <attrName> is the camelCase plural of the Kind).

    Examples:

    • resources.core.v1.Service can be accessed via resources.services
    • resources."networking.k8s.io".v1.NetworkPolicy can be accessed via resources.networkPolicies

    Note: If a resource does not have typed options defined, it cannot be patched by Nixidy and will instead be passed directly to the application output.

    applications.my-app = {
      resources.services.my-service = { ... }; # Using full path
      resources.services.my-service = { ... }; # Using alias
    };
  5. How Nixidy works

    main

    Nixidy allows you to define Kubernetes resources using Nix modules. This approach provides strong typing, composability, and reproducibility.

    1. Define: Write your Kubernetes resources (Deployments, Services, etc.) in Nix files using the applications.<name>.resources structure.
    2. Build: Use the nixidy build command to generate plain, reviewable YAML manifests.
    3. Deploy: Commit the generated YAML to your repository for Argo CD to pick up, or use nixidy apply for direct deployment.
    {
      applications.demo = {
        namespace = "demo";
        createNamespace = true;
    
        resources = {
          deployments.nginx.spec = {
            replicas = 3;
            selector.matchLabels.app = "nginx";
            template = {
              metadata.labels.app = "nginx";
              spec.containers.nginx = {
                image = "nginx:1.25.1";
                ports.http.containerPort = 80;
              };
            };
          };
    
          services.nginx.spec = {
            selector.app = "nginx";
            ports.http.port = 80;
          };
        };
      };
    }
  6. How `nixidy apply` handles Object Transforms

    main

    When using nixidy apply, any postProcess rules defined in [Object Transforms] are executed. Each object is streamed through its post-process command before being applied to the cluster. This ensures that nixidy apply deploys the exact same manifests as nixidy switch.

    Warning: The output of a postProcess command must be a valid Kubernetes manifest. If a transform produces an output that only a GitOps controller can consume (and is not a valid K8s manifest), it will only work with nixidy switch and will fail during nixidy apply.

  7. Use warnings for non-breaking notifications

    main

    Warnings are messages printed during evaluation that do not fail the build. Use warnings for deprecation notices, unusual configurations, or reminders that do not render the configuration invalid.

    Warnings can be defined in three ways:

    1. Per-application (conditional): Using applications.<name>.warnings with a when condition.
    2. Per-application (unconditional): Using a shorthand string in applications.<name>.warnings.
    3. Global: Using nixidy.warnings for system-wide notices.
    # Conditional per-application warning
    applications.my-app = {
      warnings = [
        {
          when = config.applications.my-app.createNamespace == false;
          message = "Not creating namespace for my-app, make sure it exists on the cluster";
        }
      ];
    };
    
    # Unconditional per-application warning (shorthand)
    applications.my-app = {
      warnings = [ "my-app is using a deprecated configuration, see the docs for migration steps" ];
    };
    
    # Global warning
    nixidy.warnings = [
      {
        when = config.nixidy.target.branch != "main";
        message = "Target branch is not 'main', make sure this is intentional";
      }
    ];
  8. Resolve naming conflicts in generated resource options

    main

    When multiple CRDs define the same kind (e.g., two different operators both defining a Database resource), Nixidy may generate conflicting attribute names under resources. Use the following to resolve conflicts:

    1. namePrefix: Adds a prefix to all generated attribute names.

      • Example: Setting namePrefix = "postgres"; for a Postgres operator will result in resources.postgresDatabases instead of resources.databases.
    2. attrNameOverrides: A mapping that allows you to explicitly name specific resources. This takes precedence over all other naming heuristics.

      • Format: A mapping from the CRD's <plural>.<group> to the desired attribute name.

    Example of attrNameOverrides:

    attrNameOverrides = {
      "groups.user.keycloak.crossplane.io" = "keycloakUserGroups";
    };
  9. Use assertions to enforce configuration invariants

    main

    Assertions are conditions that must be true. If an assertion fails, the Nixidy build will fail with a descriptive error message. Use assertions for invalid configurations, such as missing required resources or conflicting settings, that should prevent a build from proceeding.

    Assertions can be defined at two levels:

    1. Per-application: Defined under applications.<name>.assertions to validate settings specific to that application.
    2. Global: Defined under nixidy.assertions to validate invariants that span multiple applications or the entire configuration.
    # Per-application assertion example
    applications.my-app = {
      assertions = [
        {
          assertion = builtins.length (builtins.attrNames config.applications.my-app.resources.deployments) > 0;
          message = "my-app must have at least one deployment";
        }
      ];
    };
    
    # Global assertion example
    nixidy.assertions = [
      {
        assertion = config.applications ? app-a && config.applications ? app-b;
        message = "Both app-a and app-b must be defined";
      }
    ];
  10. Create reusable patterns with templates

    main

    Nixidy provides a template system for defining reusable deployment patterns. Templates are defined under options.templates and can be used as application imports.

    A template consists of:

    • options: Parameters that the template accepts.
    • output: A resource generator function that uses those parameters to produce Kubernetes objects.

    Example usage:

    applications.myapp.templates.webApp.frontend = {
      image = "nginx:latest";
      replicas = 3;
    };
    # Defining a template
    options.templates.webApp = submodule {
      options = { image = mkOption { ... }; replicas = mkOption { ... }; };
      output = cfg: { ... };
    };
    
    # Using a template
    applications.myapp.templates.webApp.frontend = {
      image = "nginx:latest";
      replicas = 3;
    };
  11. How the Resource Type System works

    main

    Nixidy organizes all Kubernetes resources using a Group/Version/Kind (GVK) hierarchy. This allows for strongly-typed resource definitions.

    Full GVK Path

    You can access resources using their full path:

    resources.<group>.<version>.<kind>.<name> = { ... };
    
    # Examples:
    resources.core.v1.ConfigMap.my-config = { ... };
    resources.apps.v1.Deployment.nginx = { ... };
    resources."networking.k8s.io".v1.Ingress.main = { ... };

    Aliases

    For convenience, Nixidy provides aliases to common resources:

    • resources.configMaps.<name> maps to core.v1.ConfigMap
    • resources.deployments.<name> maps to apps.v1.Deployment
    • resources.ingresses.<name> maps to networking.k8s.io.v1.Ingress
    resources.core.v1.ConfigMap.my-config = { ... };
    resources.apps.v1.Deployment.nginx = { ... };
    resources."networking.k8s.io".v1.Ingress.main = { ... };
    
    # Using aliases:
    resources.configMaps.my-config = { ... };
    resources.deployments.nginx = { ... };
    resources.ingresses.main = { ... };
  12. How Transformers work in Nixidy

    main

    Transformers are functions used to modify Kubernetes manifests for Helm releases or Kustomize applications. A transformer follows the signature [AttrSet] -> [AttrSet] (taking a list of manifests and returning a modified list).

    Lifecycle: Transformers are executed after manifests have been rendered and parsed into Nix, but before they are converted into the Nixidy form (<group>.<version>.<kind>.<name>).

    Scope:

    • Transformers act on a single Helm release or Kustomize application.
    • To modify objects across all applications in an environment (e.g., for encrypting secrets), use Object Transforms instead.

    Configuration Keys:

    • For Helm releases: #!nix nixidy.defaults.helm.transformer
    • For Kustomize applications: #!nix nixidy.defaults.kustomize.transformer
    /* Signature: [AttrSet] -> [AttrSet] */