Nixidy Architecture Guide for Contributors#
This guide provides a comprehensive overview of the nixidy codebase to help new contributors understand the project structure, key concepts, and development workflow.
Table of Contents#
- Project Structure
- Core Concepts
- Module System Deep Dive
- Library Functions
- Code Generators
- Testing Framework
- Development Workflow
- Adding New Features
- Common Tasks
- Code Style
Project Structure#
nixidy/
├── cli/ # Python-based CLI tool
├── docs/ # Documentation (MkDocs)
│ └── user_guide/ # User-facing documentation
├── lib/ # Nix function library
│ ├── default.nix # Library entry point
│ ├── helm.nix # Helm-related functions
│ ├── kube.nix # Kubernetes utility functions
│ ├── kustomize.nix # Kustomize-related functions
│ └── tests.nix # Library unit tests
├── modules/ # NixOS-style modules
│ ├── applications/ # Application submodule
│ │ ├── default.nix # Application options and config
│ │ ├── helm.nix # Helm release processing
│ │ ├── kustomize.nix # Kustomize processing
│ │ ├── lib.nix # Application helper functions
│ │ └── yamls.nix # Raw YAML processing
│ ├── generated/ # Auto-generated resource options
│ │ ├── argocd.nix # ArgoCD CRD options
│ │ └── k8s/ # Kubernetes resource options by version
│ ├── testing/ # Testing framework modules
│ │ ├── default.nix # Test suite configuration
│ │ └── eval.nix # Test evaluation logic
│ ├── applications.nix # Main applications option
│ ├── build.nix # Build output packages
│ ├── default.nix # Module entry point
│ ├── extra-files.nix # Extra files configuration
│ ├── modules.nix # Module list
│ ├── nixidy.nix # Core nixidy options
│ └── templates.nix # Template system
├── nixidy/ # Legacy bash CLI (deprecated)
├── pkgs/ # Nix packages and generators
│ └── generators/ # CRD and K8s schema generators
│ ├── crd2jsonschema.py # CRD to JSON schema converter
│ ├── default.nix # Generator entry point
│ ├── generator.nix # Nix options generator
│ └── versions.nix # Kubernetes versions config
├── tests/ # Module unit tests
│ ├── helm/ # Helm-specific tests
│ ├── kustomize/ # Kustomize-specific tests
│ └── *.nix # Individual test files
├── flake.nix # Nix flake definition
└── default.nix # Non-flake entry point
Core Concepts#
1. NixOS Module System#
Nixidy is built on top of the NixOS module system. If you're unfamiliar with it, read the NixOS Module System documentation first.
Key concepts:
- Options: Define the configuration interface with types, defaults, and descriptions
- Config: Contains the actual configuration values after module evaluation
- Imports: Modules can import other modules to extend functionality
- Special Args: Additional arguments passed to all modules (lib, pkgs, config, etc.)
2. Application Lifecycle#
User Config → Module Evaluation → Resource Processing → Manifest Generation → Output
- User Configuration: Nix files defining applications and resources
- Module Evaluation: NixOS module system merges all configurations
- Resource Processing: Helm/Kustomize/YAML converted to typed resources
- Manifest Generation: Resources serialized to YAML files
- Output: Packages created for different deployment strategies
3. Resource Type System#
Resources are organized by Group/Version/Kind (GVK):
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 provide convenient access:
resources.configMaps.my-config = { ... }; # → core.v1.ConfigMap
resources.deployments.nginx = { ... }; # → apps.v1.Deployment
resources.ingresses.main = { ... }; # → networking.k8s.io.v1.Ingress
4. Processing Pipeline#
All input sources are normalized to typed nix resources:
┌─────────────────┐
│ Helm Charts │──┐
├─────────────────┤ │
│ Kustomize │──┤──→ [GVK Classification] ──→ [Typed Nix Resources] ──→ [YAML Output]
├─────────────────┤ │
│ Raw YAML │──┘
└─────────────────┘
Module System Deep Dive#
Main Modules#
modules/applications.nix#
Defines the top-level applications option:
{
options.applications = mkOption {
type = attrsOf (submoduleWith {
modules = [ ./applications ] ++ config.nixidy.applicationImports;
specialArgs.nixidyDefaults = config.nixidy.defaults;
});
};
}
Key responsibilities:
- Creates application submodules
- Manages Kubernetes version selection (nixidy.k8sVersion)
- Imports generated resource options
modules/nixidy.nix#
Core nixidy configuration:
{
options.nixidy = {
env = mkOption { ... }; # Environment name
target.repository = mkOption { ... }; # Git repository URL
target.branch = mkOption { ... }; # Target branch
target.rootPath = mkOption { ... }; # Root path for manifests
defaults = { ... }; # Default settings
appOfApps = { ... }; # Bootstrap app config
charts = mkOption { ... }; # Helm chart sources
};
}
Also handles: - App-of-apps pattern generation - Chart attribute set building - Public apps list management
modules/applications/default.nix#
Defines individual application options:
{
options = {
name = mkOption { ... };
namespace = mkOption { ... };
createNamespace = mkOption { ... };
syncPolicy = { ... };
destination = { ... };
resources = { ... }; # Typed Kubernetes resources
objects = mkOption { ... }; # Internal: final resource list
};
}
Key responsibilities: - Application metadata and settings - Sync policy configuration - Resource type registration - Final object list generation
modules/applications/helm.nix#
Helm chart integration:
{
options.helm.releases = mkOption {
type = attrsOf (submodule {
options = {
chart = mkOption { ... };
values = mkOption { ... };
transformer = mkOption { ... };
};
});
};
}
Processing flow:
1. helm.buildHelmChart templates the chart
2. builtins.readFile reads output
3. kube.fromYAML parses to attribute sets
4. transformer function applied
5. Objects grouped by GVK
6. Added to resources or objects
modules/applications/kustomize.nix#
Kustomize integration (similar pattern to Helm):
{
options.kustomize.applications = mkOption {
type = attrsOf (submodule {
options = {
kustomization.src = mkOption { ... };
kustomization.path = mkOption { ... };
transformer = mkOption { ... };
};
});
};
}
modules/applications/yamls.nix#
Raw YAML manifest support:
{
options.yamls = mkOption {
type = listOf str;
description = "List of YAML manifest strings";
};
}
modules/build.nix#
Creates output packages:
environmentPackage: All application manifests combinedactivationPackage: Fornixidy switchoperationsdeclarativePackage: Forkubectl apply --prunebootstrapPackage: App-of-apps manifest
modules/templates.nix#
Template system for reusable patterns:
{
options.templates = mkOption {
type = attrsOf (submodule {
options = {
options = mkOption { ... }; # Template parameters
output = mkOption { ... }; # Resource generator function
};
});
};
}
Templates become application imports, allowing:
applications.myapp.templates.webApp.frontend = {
image = "nginx:latest";
replicas = 3;
};
Helper Functions (modules/applications/lib.nix)#
{
# Extract Group/Version/Kind from Kubernetes object
getGVK = object: {
group = ...; # "core" for v1, otherwise first part of apiVersion
version = ...; # Version string
kind = ...; # Kind string
};
# Flatten *List objects (e.g., ConfigMapList → [ConfigMap, ...])
flattenListObjects = ...;
}
Library Functions#
Entry Point (lib/default.nix)#
Extends nixpkgs.lib with nixidy functions:
lib.extend (self: old: {
kustomize = import ./kustomize.nix { ... };
helm = import ./helm.nix { ... };
kube = import ./kube.nix { ... };
})
Helm Functions (lib/helm.nix)#
| Function | Description |
|---|---|
downloadHelmChart |
Downloads chart from Helm registry |
buildHelmChart |
Templates chart with values |
getChartValues |
Parses chart's default values.yaml |
mkChartAttrs |
Creates chart attrset from directory structure |
Kube Functions (lib/kube.nix)#
| Function | Description |
|---|---|
fromYAML |
Parses YAML string to attribute sets |
fromOctal |
Converts octal string to integer |
removeLabels |
Removes specified labels from manifests |
Kustomize Functions (lib/kustomize.nix)#
| Function | Description |
|---|---|
buildKustomization |
Builds kustomize application |
Code Generators#
Overview (pkgs/generators/)#
Nixidy generates typed Nix options from: 1. Kubernetes OpenAPI schemas 2. Custom Resource Definitions (CRDs)
Kubernetes Schema Generation#
pkgs/generators/default.nix handles K8s schema generation:
- Fetches Kubernetes source for each version in
versions.nix - Extracts OpenAPI swagger spec
- Generates namespaced resource info
- Produces Nix options via
generator.nix
Output: modules/generated/k8s/v1.XX.nix
CRD Generation#
Two entry points:
fromCRD#
fromCRD {
name = "cilium";
src = pkgs.fetchFromGitHub { ... };
crds = [ "path/to/crd.yaml" ];
namePrefix = ""; # Optional prefix for attribute names
attrNameOverrides = { }; # Manual name overrides
skipCoerceToList = { }; # Skip list coercion for specific fields
}
fromChartCRD#
fromChartCRD {
name = "cert-manager";
chartAttrs = { repo = "..."; chart = "..."; version = "..."; };
crds = [ "Certificate" ]; # Filter by kind
}
CRD Processing (crd2jsonschema.py)#
Python script that:
1. Reads CRD YAML files
2. Extracts OpenAPI v3 schemas
3. Flattens $ref references
4. Outputs JSON schema for generator.nix
Testing Framework#
Module Tests (tests/)#
Located in tests/, using nixidy's testing framework.
Test Structure#
# tests/my-feature.nix
{
lib,
config,
...
}:
let
apps = config.applications;
in
{
# Define test configuration
applications.test1 = {
namespace = "test";
resources.configMaps.cm.data.FOO = "bar";
};
# Define test assertions
test = {
name = "my feature test";
description = "Description of what's being tested";
assertions = [
{
description = "ConfigMap should have FOO key";
expression = (elemAt apps.test1.objects 0).data;
expected = { FOO = "bar"; };
}
{
description = "Custom assertion function";
expression = apps.test1.objects;
assertion = objs: length objs == 1;
}
];
};
}
Running Tests#
# Run module tests
nix run .#moduleTests
# Run library tests
nix run .#libTests
Test Registration (tests/default.nix)#
{
testing = {
name = "nixidy modules";
tests = [
./configmap.nix
./create-namespace.nix
./helm/with-values.nix
# ... more tests
];
};
}
Library Tests (lib/tests.nix)#
Uses lib.runTests pattern:
{
kube = {
fromYAML = {
testSingleObject = {
expr = lib.kube.fromYAML "...";
expected = [ { ... } ];
};
};
removeLabels = {
testLabelPresent = {
expr = lib.kube.removeLabels ["helm.sh/chart"] { ... };
expected = { ... };
};
};
};
}
Development Workflow#
Prerequisites#
- Nix with flakes enabled
- Basic understanding of NixOS module system
Common Commands#
# Format code
nix fmt
# Run static linter
nix run .#staticCheck
# Run library tests
nix run .#libTests
# Run module tests
nix run .#moduleTests
# Generate Kubernetes modules
nix run .#generate
# Serve documentation locally
nix run .#docsServe
Development Cycle#
- Make changes to modules or library
- Write tests for new functionality
- Run tests to verify changes
- Format code with
nix fmt - Run linter with
nix run .#staticCheck - Test manually with a sample configuration
Manual Testing#
Create a test configuration:
# test-config.nix
{
nixidy.target = {
repository = "https://github.com/test/repo.git";
branch = "main";
};
applications.test = {
namespace = "test";
createNamespace = true;
resources.deployments.nginx.spec = {
selector.matchLabels.app = "nginx";
template = {
metadata.labels.app = "nginx";
spec.containers.nginx.image = "nginx:latest";
};
};
};
}
Build and inspect:
nix run .#cli -- build .#test
tree result/
cat result/test/Deployment-nginx.yaml
Adding New Features#
Adding a New Application Option#
- Define the option in
modules/applications/default.nix:
{
options = {
myNewOption = mkOption {
type = types.bool;
default = false;
description = "Description of the option.";
};
};
}
- Use the option in config:
{
config = lib.mkIf config.myNewOption {
# Configuration when option is enabled
};
}
- Write tests in
tests/:
# tests/my-new-option.nix
{
applications.test1 = {
myNewOption = true;
# ...
};
test = {
name = "my new option";
description = "Test the new option";
assertions = [ ... ];
};
}
-
Register test in
tests/default.nix -
Document in
docs/user_guide/
Adding a New Library Function#
- Add function to appropriate file in
lib/:
# lib/kube.nix
{
myNewFunction =
# Parameter description
param:
# Implementation
...;
}
- Add documentation as comments:
/*
Description of function.
Type:
myNewFunction :: ParamType -> ReturnType
Example:
myNewFunction "input"
=> "output"
*/
myNewFunction = ...;
- Write tests in
lib/tests.nix:
{
kube = {
myNewFunction = {
testBasicCase = {
expr = lib.kube.myNewFunction "input";
expected = "output";
};
};
};
}
Adding a New Resource Processor#
Similar to Helm/Kustomize, create a new module:
- Create module
modules/applications/myprocessor.nix:
{
nixidyDefaults,
lib,
config,
...
}:
let
helpers = import ./lib.nix lib;
in
{
options.myProcessor = mkOption {
type = with types; attrsOf (submodule { ... });
};
config = {
# Process inputs and add to resources/objects
resources = mkMerge [ ... ];
objects = [ ... ];
};
}
- Import in
modules/applications/default.nix:
{
imports = [
./helm.nix
./kustomize.nix
./yamls.nix
./myprocessor.nix # Add here
];
}
Common Tasks#
Updating Kubernetes Versions#
- Edit
pkgs/generators/versions.nix:
{
"1.34.0" = {
hash = "sha256-...";
spec = "api/openapi-spec/swagger.json";
discovery = {
core = "api/discovery/core_v1.json";
aggregated = "api/discovery/aggregated_v2.json";
};
};
}
- Regenerate:
nix run .#generate
- Update default version in
modules/applications.nixif needed
Adding a New Sync Option#
- Add option in
modules/applications/default.nixundersyncPolicy.syncOptions:
{
syncPolicy.syncOptions.myOption = mkOption {
type = types.bool;
default = false;
apply = val: if val then "MyOption=true" else null;
description = "Description";
};
}
-
The
applyfunction converts to ArgoCD sync option format -
convertSyncOptionsListautomatically collects non-null options
Debugging Module Evaluation#
Use builtins.trace for debugging:
{
config = lib.mkMerge [
(builtins.trace "Processing ${config.name}" {
# ...
})
];
}
Or use lib.debug.traceValSeqN:
{
objects = lib.debug.traceValSeqN 2 config.resources;
}
Code Style#
Nix#
- Format: Use
nix fmt(nixfmt-rfc-style) - Sorting: Keep attribute sets alphabetically sorted
- Inherit: Use
inheritwhere possible to reduce verbosity - Imports: Group imports logically
- Types: Use specific types over
types.anythingwhen possible
# Good
{
lib,
config,
pkgs,
...
}:
let
inherit (config) namespace;
inherit (lib) mkOption types;
in
{
options.myOption = mkOption {
type = types.str;
default = "";
};
}
# Avoid
{lib, config, pkgs, ...}: let
namespace = config.namespace;
in {
options.myOption = lib.mkOption {
type = lib.types.str;
default = "";
};
}
Python (CLI)#
- Follow PEP 8 guidelines
- Use type hints for all function signatures
- Document public functions with docstrings
Documentation#
- Use MkDocs syntax
- Include code examples with syntax highlighting
- Cross-reference related documentation
- Keep language clear and concise
Getting Help#
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: nixidy.dev
Summary#
Key files for different tasks:
| Task | Files |
|---|---|
| Add application option | modules/applications/default.nix |
| Add nixidy option | modules/nixidy.nix |
| Add library function | lib/*.nix |
| Add resource processor | modules/applications/ |
| Add template feature | modules/templates.nix |
| Modify build output | modules/build.nix |
| Add K8s version | pkgs/generators/versions.nix |
| Write module test | tests/*.nix, tests/default.nix |
| Write library test | lib/tests.nix |
Welcome to the nixidy project! We look forward to your contributions.