Developer Guide
This guide provides comprehensive information for developers who want to understand, modify, or extend the Subnetter codebase.
Table of Contents
- Project Structure
- Development Setup
- Build Process
- Development Workflow
- Testing
- Code Style and Conventions
- Contribution Guidelines
- Release Process
- Common Development Tasks
- CI/CD Pipeline
- Core Allocation System
Project Structure
Subnetter is organized as a monorepo with multiple packages:
subnetter/├── packages/│ ├── core/ # Core CIDR allocation engine│ │ ├── src/│ │ │ ├── allocator/ # CIDR allocation logic│ │ │ ├── config/ # Configuration loading and validation│ │ │ ├── models/ # TypeScript interfaces│ │ │ ├── output/ # Output generation (CSV)│ │ │ └── index.ts # Public API exports│ │ ├── tests/ # Unit tests for core│ │ ├── package.json│ │ └── tsconfig.json│ ││ ├── cli/ # Command-line interface│ │ ├── src/│ │ │ ├── commands/ # CLI command implementations│ │ │ └── index.ts # CLI entry point│ │ ├── tests/ # Unit tests for CLI│ │ ├── package.json│ │ └── tsconfig.json│ ││ ├── cidr-utils/ # Low-level CIDR utilities│ │ ├── src/│ │ │ ├── calculator.ts # CIDR calculations│ │ │ ├── ip.ts # IP address manipulation│ │ │ ├── parser.ts # CIDR parsing│ │ │ ├── subnet.ts # Subnet allocation│ │ │ ├── types.ts # TypeScript interfaces│ │ │ ├── validator.ts # CIDR validation│ │ │ └── index.ts # Public API exports│ │ ├── tests/ # Unit tests for utilities│ │ ├── package.json│ │ └── tsconfig.json│ ││ └── docs/ # Documentation site│ ├── src/│ │ ├── content/ # Documentation content│ │ ├── components/ # Astro components│ │ └── styles/ # CSS stylesheets│ ├── public/ # Static assets│ ├── package.json│ └── astro.config.mjs # Astro configuration│├── __tests__/ # End-to-end tests│ └── e2e/ # End-to-end test files│├── .github/ # GitHub Actions workflows├── config/ # Build and configuration files├── scripts/ # Utility scripts├── examples/ # Example configurations├── tsconfig.json # Root TypeScript configuration├── package.json # Root package.json with workspaces└── README.md # Project documentation
Development Setup
Prerequisites
- Node.js (^18.18.0 || ^20.9.0 || >=21.1.0)
- Yarn (the project uses Yarn Berry 4.7.0 via the yarn releases file)
Installing and Configuring Yarn
This project uses Yarn with the zero-install configuration (yarn files are checked into the repository). We also use Yarn Workspaces to manage the monorepo structure. This section provides instructions for installing and configuring Yarn on various operating systems.
macOS
-
Install Node.js using Homebrew:
Terminal window brew install node@20 -
No need to install Yarn globally as the project uses Yarn from the
.yarn/releases
directory. -
Verify you can use Yarn in the project:
Terminal window # After cloning the repositorycd subnetter./yarn --version
Linux (Ubuntu/Debian)
-
Install Node.js:
Terminal window # Add NodeSource repositorycurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -# Install Node.jssudo apt-get install -y nodejs -
No need to install Yarn globally as the project uses Yarn from the
.yarn/releases
directory.
Linux (Fedora/RHEL)
-
Install Node.js:
Terminal window # Enable NodeSource repositorysudo dnf module enable nodejs:20# Install Node.jssudo dnf install -y nodejs -
No need to install Yarn globally as the project uses Yarn from the
.yarn/releases
directory.
NixOS
-
Add Node.js to your configuration:
In your
configuration.nix
:environment.systemPackages = with pkgs; [nodejs_20];Or if using home-manager, in your
home.nix
:home.packages = with pkgs; [nodejs_20]; -
No need to install Yarn globally as the project uses Yarn from the
.yarn/releases
directory.
Windows
-
Install Node.js:
- Download the Node.js installer from the official website
- Run the installer and follow the installation wizard
- Make sure to check the box for “Automatically install the necessary tools”
-
No need to install Yarn globally as the project uses Yarn from the
.yarn/releases
directory.
WSL2 (Windows Subsystem for Linux)
-
Install WSL2 if you haven’t already:
Terminal window # In PowerShell (Admin)wsl --install -
Install your preferred Linux distribution from the Microsoft Store
-
Follow the Linux installation instructions above for your specific distribution
Understanding the Yarn Zero-Install Configuration
This project uses Yarn with “Zero-Install” configuration, meaning:
-
The
.yarn/releases/yarn-sources.cjs
file is checked into the repository -
The
.yarnrc.yml
file configures Yarn with these key settings:compressionLevel: mixedenableGlobalCache: falsenodeLinker: node-modulesyarnPath: .yarn/releases/yarn-sources.cjscompressionLevel: mixed
optimizes the compression of dependenciesnodeLinker: node-modules
uses the classic node_modules resolution strategyenableGlobalCache: false
ensures dependencies are stored project-locallyyarnPath
specifies the location of the Yarn binary included in the repo
-
The
package.json
configures workspaces:"workspaces": ["packages/*"]
This configuration provides several benefits:
- No global Yarn installation needed: The project uses the version from
.yarn/releases
- Deterministic builds: Identical dependency installation across all environments
- Simplified onboarding: New contributors don’t need to install Yarn separately
- Workspace management: Packages can reference each other and share dependencies
Getting Started
Follow these steps to set up your development environment:
# Clone the repositorygit clone https://github.com/gangster/subnetter.gitcd subnetter
# Install dependencies./yarn install
# Build all packages./yarn build
# Run tests to ensure everything works./yarn test
Build Process
Subnetter uses TypeScript’s project references for a structured, efficient build process.
Understanding TypeScript Project References
Project references allow dependencies between TypeScript projects:
- The root
tsconfig.json
configures shared settings and references other packages - Each package has its own
tsconfig.json
that extends the root configuration - When you build a package, TypeScript automatically builds its dependencies first
This provides faster builds, proper encapsulation, and better organization.
Building the Project
# Build all packages./yarn build
# Build individual packages./yarn build:cidr-utils./yarn build:core./yarn build:cli./yarn build:docs
# Clean build artifacts./yarn clean
Package-Specific Build Scripts
Each package has its own build scripts defined in its package.json
:
-
Core Package: Simple TypeScript compilation
"scripts": {"build": "tsc -b","clean": "rm -rf dist tsconfig.tsbuildinfo"} -
CLI Package: TypeScript compilation plus executable script generation
"scripts": {"build": "tsc -b && yarn build:bin","build:bin": "node scripts/create-bin.js"} -
Docs Package: TypeScript compilation plus static site generation
"scripts": {"build": "yarn generate-api-docs && astro build && node postbuild.js"}
Build Process Flow
-
When you run
yarn build
, it builds packages in the correct dependency order:- First the cidr-utils package is built (
@subnetter/cidr-utils
) - Then the core package is built (
@subnetter/core
), which depends on cidr-utils - Then the CLI package (
@subnetter/cli
), which depends on core - Documentation can be built separately
- First the cidr-utils package is built (
-
The TypeScript compiler follows project references to ensure dependent projects are built first.
-
Each package’s build process:
- Compiles TypeScript to JavaScript
- Generates type declaration files (
.d.ts
) - Creates source maps for debugging
CLI Binary Generation
The CLI package includes a special step to create an executable binary:
- The
build:bin
script runscreate-bin.js
which:- Creates a
bin
directory - Generates a
subnetter
executable file with a Node.js shebang - Sets executable permissions (chmod 755)
- Creates a
#!/usr/bin/env node
const fs = require("fs");const path = require("path");
// Define pathsconst binDir = path.resolve(__dirname, "../bin");const binFile = path.resolve(binDir, "subnetter");
// Create bin directory if it doesn't existif (!fs.existsSync(binDir)) { fs.mkdirSync(binDir, { recursive: true });}
// Create the bin file with the appropriate shebangconst binContent = `#!/usr/bin/env node
require("../dist/index.js");`;
// Write the bin filefs.writeFileSync(binFile, binContent);
// Make the bin file executablefs.chmodSync(binFile, "755");
- The CLI package.json specifies this binary in the
bin
field:
"bin": { "subnetter": "./bin/subnetter.js"}
- When the package is installed globally, npm/yarn creates a symlink to this binary in the user’s PATH.
Development Mode
For active development, use the watch mode:
./yarn dev
This starts TypeScript in watch mode, rebuilding files as you make changes.
Development Workflow
Branch Strategy
We follow the GitHub Flow branching strategy:
- Create a feature branch from
main
- Make your changes
- Open a pull request to
main
- After review, merge the pull request
Commit Messages
We use Conventional Commits for commit messages. This helps generate accurate changelogs and determine version bumps.
Examples:
feat: add new subnet type validation
fix: correct CIDR overlapping detection
docs: update installation instructions
test: add test for edge case in CIDR allocation
refactor: simplify allocation algorithm
The commit message format is enforced by commitlint, which runs as a pre-commit hook.
Pull Request Process
- Ensure your code passes all tests and linting
- Update documentation if necessary
- Open a pull request with a clear description of the changes
- Address review comments
- Once approved, the pull request will be merged
Testing
Subnetter has a comprehensive testing strategy that includes multiple levels of testing.
Unit Tests
Unit tests focus on individual components and are located in the tests
directory of each package.
To run unit tests:
yarn test # Run all testsyarn test:coverage # Run tests with coverage reporting
To run tests for a specific package:
yarn workspace @subnetter/core testyarn workspace @subnetter/cli test
End-to-End Tests
End-to-end tests verify the complete workflow and are located in the __tests__/e2e
directory.
To run end-to-end tests:
yarn test:e2e
Test Structure
Tests are organized by package and follow a consistent structure:
- Unit Tests: Test individual functions and classes
- Located in
packages/<package>/tests/
- Named
*.test.ts
- Located in
- End-to-End Tests: Test complete workflows
- Located in
__tests__/e2e/
- Named
*.test.ts
- Located in
Testing Guidelines
- Every feature should have corresponding tests
- Aim for high test coverage (>90% for core functionality)
- Use descriptive test names that indicate what is being tested
- Use mocks sparingly and only when necessary
- End-to-end tests should use realistic configurations
Code Style and Conventions
TypeScript and ESLint
We use TypeScript for type safety and ESLint for static code analysis. Our configuration is based on recommended settings with custom rules specified in eslint.config.js
.
To lint your code:
yarn lint # Check for linting issuesyarn lint:fix # Automatically fix linting issues
We use TypeScript 5.8.x for the core package and 5.7.x for other packages.
Code Formatting
We use Prettier for code formatting. The configuration is in .prettierrc.json
.
Naming Conventions
- Files: Use kebab-case for filenames (e.g.,
cidr-allocator.ts
) - Classes: Use PascalCase for class names (e.g.,
CidrAllocator
) - Functions and Variables: Use camelCase for functions and variables (e.g.,
processRegions
) - Interfaces: Use PascalCase for interfaces (e.g.,
Allocation
) - Enums: Use PascalCase for enums and enum members
Documentation
- Use JSDoc comments for all public APIs
- Include examples in documentation when helpful
- Keep documentation up to date with the code
Contribution Guidelines
We welcome contributions from the community! Here’s how you can contribute:
Finding Issues to Work On
- Check the GitHub Issues for tasks
- Look for issues labeled “good first issue” for a good starting point
Submitting Changes
- Fork the repository
- Create a feature branch from
main
- Make your changes
- Ensure your code passes tests and linting
- Open a pull request
- Address review comments
Code Review Process
All code changes go through a review process:
- Automated checks (CI/CD pipeline)
- Code review by at least one maintainer
- Address feedback
- Final approval and merge
Release Process
We use semantic versioning (SemVer) for releases.
Version Bumping
Versions are bumped automatically based on commit messages:
fix
: Patch version bumpfeat
: Minor version bumpfeat!
orfix!
: Major version bump (breaking changes)
Release Workflow
- Merge pull requests to
main
- The CI/CD pipeline runs tests on multiple platforms
- If all tests pass, the release job creates releases automatically
- Packages are published to GitHub Packages
- Release notes are generated based on commit messages
Package Publishing
Our packages are published to GitHub Packages. The package configuration includes:
"publishConfig": { "registry": "https://npm.pkg.github.com", "access": "public"}
Manual Release
If necessary, manual releases can be triggered:
npm run release
Common Development Tasks
Adding a New Feature
- Understand the requirements and design the feature
- Create a feature branch from
main
- Implement the feature with tests
- Update documentation
- Open a pull request
Debugging
For debugging, you can use:
# Running with verbose outputyarn workspace @subnetter/cli develop -- generate -c config.json -o output.csv -v
# Node.js inspectornode --inspect-brk packages/cli/dist/index.js generate -c config.json
Adding a New Command
To add a new CLI command:
- Create a new file in
packages/cli/src/commands/
- Implement the command using the Commander.js API
- Register the command in
packages/cli/src/index.ts
- Add tests for the command
- Update the documentation
Extending the Core Functionality
To extend the core functionality:
- Identify where your feature fits in the architecture
- Add new interfaces or types in
packages/core/src/models/
- Implement the functionality in the appropriate directory
- Export the new functionality in
packages/core/src/index.ts
- Add tests for the new functionality
- Update the documentation
Advanced Topics
Performance Optimization
When optimizing for performance:
- Use profiling tools to identify bottlenecks
- Consider memoization for expensive calculations
- Optimize CIDR subdivision for large allocations
- Use efficient data structures for lookup operations
Adding Support for New Cloud Providers
To add a new cloud provider:
- Update the CloudProvider interface
- Extend the provider detection logic in
getProviderForRegion()
- Add provider-specific AZ naming in
generateAzNames()
- Add tests for the new provider
- Update documentation
Extending Output Formats
To add a new output format:
- Create a new file in
packages/core/src/output/
- Implement the output generation function
- Export the function in
packages/core/src/index.ts
- Add options to the CLI in
packages/cli/src/index.ts
- Add tests for the new format
Troubleshooting
Common Build Issues
- TypeScript Errors: Check if your TypeScript version matches the project requirements
- Missing Dependencies: Run
yarn install
to ensure all dependencies are installed - Out-of-Memory: Increase Node.js memory limit with
NODE_OPTIONS=--max_old_space_size=4096
Testing Issues
- Failed Tests: Check the error message for details
- Timeout Issues: Consider increasing the test timeout value
- Coverage Issues: Ensure you have written tests for all code paths
CI/CD Pipeline
Subnetter uses GitHub Actions for continuous integration and deployment:
Workflow Overview
The main CI/CD pipeline is defined in .github/workflows/ci-cd.yml
and includes:
-
Validating Pull Requests:
- Checks for package-lock.json files (using yarn instead)
- Validates commit messages follow conventional commits
-
Testing:
- Linting
- Building all packages
- Running unit tests
- Running end-to-end tests
-
Releasing:
- Automatic version bumping based on commit types
- Publishing to GitHub Packages
-
Documentation:
- Building and deploying to GitHub Pages
Environment Configuration
The CI/CD pipeline runs on GitHub-hosted runners with the following configuration:
- Node.js: Version 20.x
- Operating System: Ubuntu Latest
- Package Manager: Yarn Berry
Workflow Triggers
The workflow is triggered by:
- Pull Requests to the
main
branch - Pushes to the
main
branch
Workflow Jobs
-
validate-pr:
- Validates PR structure and commits
- Checks that package-lock.json is not present
- Ensures commit messages follow conventional commit format
-
test-pr:
- Runs on pull requests after validation
- Sets up Node.js 20.x and Yarn
- Runs linting
- Builds all packages
- Executes unit tests and E2E tests
-
test-main:
- Similar to test-pr but runs on pushes to main
- Required for the release process
-
check-release:
- Determines if a release is needed based on commit messages
- Runs after tests pass on the main branch
-
release:
- Creates a new release if required
- Automatically bumps version based on commit types
- Publishes packages to GitHub Packages
-
docs:
- Builds and deploys documentation to GitHub Pages
- Triggered by changes to documentation files on main
Example Workflow Configuration
name: CI/CD Pipeline
on: push: branches: [main] pull_request: branches: [main]
jobs: validate-pr: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 # Additional steps...
test-pr: needs: validate-pr if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 # Testing steps...
# Additional jobs...
Debugging CI/CD Issues
If you encounter CI/CD pipeline failures:
- Check the GitHub Actions logs for detailed error information
- Ensure your local environment matches the CI environment (Node.js 20.x)
- Run tests locally with
yarn test
to reproduce issues - For release failures, check commit message formats and versioning
Troubleshooting
Common Issues and Solutions
Build Errors
Problem: TypeScript build errors in dependencies. Solution:
# Clean build artifacts and rebuild./yarn clean./yarn build
Problem: Unable to resolve dependencies between packages. Solution: Check package.json references and ensure the dependency tree is correct.
Test Failures
Problem: Tests pass locally but fail in CI. Solution: Ensure you’re using Node.js 20.x locally to match the CI environment.
Problem: Flaky tests that sometimes pass, sometimes fail. Solution: Investigate race conditions or timing issues in the tests.
Linting Issues
Problem: ESLint or Prettier errors. Solution:
# Fix linting issues automatically where possible./yarn lint:fix
Release Issues
Problem: Release not triggered despite merged PR. Solution: Ensure at least one commit follows the conventional commit format that triggers a version bump.
Additional Resources
- TypeScript Project References
- Yarn Workspaces Documentation
- Conventional Commits Specification
- GitHub Actions Documentation
Working with the CIDR Utilities Package
The @subnetter/cidr-utils
package provides low-level utilities for IP address and CIDR manipulation. This package can be used both internally (by the core package) or as a standalone utility library for other projects.
Architecture and Design
The CIDR utils package was designed with the following principles:
- Zero dependencies: Pure JavaScript/TypeScript implementation without external dependencies
- TypeScript-first: Comprehensive type definitions for all functions and data structures
- Comprehensive API: Complete set of utilities for all common CIDR operations
- Modular design: Each file has a single responsibility and a clean API
- Thorough testing: Extensive unit test coverage, including edge cases
Key Components
-
IP Address Manipulation (
ip.ts
): Functions to convert between string, numeric, and octet representationsipv4ToNumber
: Convert an IPv4 address string to its numeric representationnumberToIpv4
: Convert a numeric representation to an IPv4 address stringcreateIpAddress
: Create an IP address object with various formats
-
CIDR Parsing (
parser.ts
): Functions to parse and normalize CIDR notationparseIpv4Cidr
: Parse a CIDR string into its components (address and prefix length)normalizeCidr
: Normalize a CIDR string to its canonical form
-
CIDR Validation (
validator.ts
): Functions to validate CIDR notationisValidIpv4Cidr
: Check if a string is a valid CIDRvalidateIpv4Cidr
: Validate a CIDR with detailed error reporting
-
Subnet Calculations (
calculator.ts
): Functions for subnet calculationscalculateNetworkAddress
: Calculate the network address of a CIDRcalculateBroadcastAddress
: Calculate the broadcast address of a CIDRcalculateUsableIps
: Calculate the number of usable IPs in a CIDRisIpInCidr
: Check if an IP address is within a CIDR rangedoCidrsOverlap
: Check if two CIDRs overlap
-
Subnet Allocation (
subnet.ts
): Functions for subnet allocationsubdivideIpv4Cidr
: Divide a CIDR into smaller subnetsfindNextAvailableCidr
: Find the next available CIDR block in a range
Using the CIDR Utils Package
You can use the CIDR utils package directly in your code:
// Import specific functionsimport { isValidIpv4Cidr, calculateUsableIps, ipv4ToNumber} from '@subnetter/cidr-utils';
// Validate a CIDRconst validCidr = isValidIpv4Cidr('10.0.0.0/24'); // true
// Calculate usable IPsconst usableIps = calculateUsableIps('10.0.0.0/24'); // 254
// Convert IP to numberconst ipNumber = ipv4ToNumber('10.0.0.1'); // 167772161
Extending CIDR Utils
When adding new functionality to the CIDR utils package:
- Identify the appropriate file based on the type of functionality
- Add well-documented, strongly-typed functions
- Write comprehensive tests, including edge cases
- Export the new functions in the
index.ts
file - Update dependencies (if needed) in the core and CLI packages
Build Considerations
The cidr-utils
package sits at the bottom of the dependency tree, so it needs to be built first:
# Build cidr-utils first./yarn build:cidr-utils
# Then build dependent packages./yarn build:core./yarn build:cli
The CI/CD pipeline is configured to build packages in the correct order automatically.
Core Allocation System
The heart of Subnetter is its CIDR allocation system, which is built around two main classes: HierarchicalAllocator
and ContiguousAllocator
. These classes work together to provide a deterministic, hierarchical allocation of CIDR blocks.
ContiguousAllocator
The ContiguousAllocator
is responsible for allocating CIDR blocks sequentially from a base CIDR block. It ensures that all allocations are contiguous and do not overlap.
Design Principles
- Deterministic Allocation: The allocator always produces the same output for the same input.
- Sequential Allocation: CIDR blocks are allocated in sequence, without gaps.
- Validation: The allocator validates that requested allocations fit within the available space.
Implementation Details
export class ContiguousAllocator { private baseCidr: string; private currentIp: bigint; private endIp: bigint; private allocated: string[] = [];
constructor(baseCidr: string) { validateCidr(baseCidr); this.baseCidr = baseCidr;
// Calculate the start and end IP addresses for the base CIDR const [baseIp, prefixLength] = this.baseCidr.split("/"); const ipInt = ipToInt(baseIp); const maskLength = parseInt(prefixLength, 10);
this.currentIp = ipInt; this.endIp = ipInt + (1n << BigInt(32 - maskLength)) - 1n; }
allocate(prefixLength: number): string { // Calculate required size const size = 1n << BigInt(32 - prefixLength);
// Align the current IP to the required boundary const alignedIp = this.currentIp + ((size - (this.currentIp % size)) % size);
// Check if there's enough space if (alignedIp + size - 1n > this.endIp) { throw new Error(`Not enough space left for allocation with prefix /${prefixLength}`); }
// Create the CIDR const cidr = `${intToIp(alignedIp)}/${prefixLength}`;
// Update the current IP this.currentIp = alignedIp + size;
// Record the allocation this.allocated.push(cidr);
return cidr; }
// ... other methods (reset, getAvailableSpace, getAllocated) ...}
Key Methods
- allocate(prefixLength): Allocates a CIDR block with the specified prefix length
- reset(): Resets the allocator to its initial state
- getAvailableSpace(): Returns the amount of space still available
- getAllocated(): Returns all allocated CIDR blocks
HierarchicalAllocator
The HierarchicalAllocator
builds on the ContiguousAllocator
to provide a hierarchical allocation strategy that follows the account > region > availability zone > subnet hierarchy.
Design Principles
- Hierarchical Structure: Allocations follow a strict hierarchy
- Override Support: Allows overriding allocations at any level
- Configuration-Driven: Allocations are driven by a configuration object
Implementation Details
export class HierarchicalAllocator { private config: Config; private rootAllocator: ContiguousAllocator; private allocations: Allocation[] = [];
constructor(config: Config) { this.config = config; validateCidr(config.baseCidr); this.rootAllocator = new ContiguousAllocator(config.baseCidr); }
generateAllocations(): Allocation[] { // Reset state this.allocations = []; this.rootAllocator.reset();
// Process each account for (const account of this.config.accounts) { this.processAccount(account); }
return this.allocations; }
private processAccount(account: Account): void { // Check if the account has a base CIDR override let accountAllocator: ContiguousAllocator;
if (account.baseCidr) { // Use the override accountAllocator = new ContiguousAllocator(account.baseCidr); } else { // Allocate from the root const accountCidr = this.rootAllocator.allocate(this.config.prefixLengths.account); accountAllocator = new ContiguousAllocator(accountCidr); }
// Process each cloud provider for this account for (const [cloudKey, cloud] of Object.entries(account.clouds)) { this.processCloud(account, cloudKey, cloud, accountAllocator); } }
private processCloud(account: Account, cloudKey: string, cloud: CloudConfig, accountAllocator: ContiguousAllocator): void { // Similar logic for cloud, region, AZ, and subnet levels... // ... see actual implementation for full details }}
Key Methods
- generateAllocations(): Generates all allocations based on the configuration
- processAccount(account): Processes an account and its children
- processCloud(account, cloudKey, cloud, allocator): Processes a cloud provider
- processRegion(account, cloudKey, cloud, region, allocator): Processes a region
- processAZ(account, cloudKey, cloud, region, az, allocator): Processes an availability zone
- addAllocation(allocation): Adds an allocation to the results
Integration and Usage
The allocation system is integrated into the Subnetter CLI and can be used programmatically:
import { HierarchicalAllocator } from '@subnetter/core';
// Create a configurationconst config = { baseCidr: '10.0.0.0/8', prefixLengths: { account: 16, region: 20, az: 24 }, accounts: [ // ... account configuration ], subnetTypes: { // ... subnet types }};
// Create the allocatorconst allocator = new HierarchicalAllocator(config);
// Generate allocationsconst allocations = allocator.generateAllocations();
// Use the allocationsconsole.log(allocations);
Extending the Allocation System
The allocation system is designed to be extensible. Here are some ways to extend it:
Custom Allocation Strategies
You can create custom allocation strategies by implementing the allocator interface:
interface IAllocator { allocate(prefixLength: number): string; reset(): void; getAvailableSpace(): bigint; getAllocated(): string[];}
// Example: An allocator that allocates from the end of the CIDR blockexport class ReverseAllocator implements IAllocator { private baseCidr: string; private currentIp: bigint; private startIp: bigint; private allocated: string[] = [];
constructor(baseCidr: string) { validateCidr(baseCidr); this.baseCidr = baseCidr;
const [baseIp, prefixLength] = this.baseCidr.split("/"); const ipInt = ipToInt(baseIp); const maskLength = parseInt(prefixLength, 10);
this.startIp = ipInt; this.currentIp = ipInt + (1n << BigInt(32 - maskLength)) - 1n; }
allocate(prefixLength: number): string { // Allocate from the end of the CIDR block // ... implementation details }
// ... other methods}
Custom Hierarchy Levels
The hierarchical allocator supports the default hierarchy (account > cloud > region > AZ > subnet), but you can create a custom hierarchy by implementing a new allocator:
export class CustomHierarchicalAllocator { // ... similar to HierarchicalAllocator, but with custom hierarchy levels
generateAllocations(): Allocation[] { // Process custom hierarchy levels // ... }}
Integration with Other Systems
The allocation system can be integrated with other systems, such as Terraform or CloudFormation:
export class TerraformGenerator { private allocations: Allocation[];
constructor(allocations: Allocation[]) { this.allocations = allocations; }
generateTerraform(): string { // Generate Terraform code from allocations // ... }}
Testing Allocation Algorithms
The allocation system is extensively tested using Jest. Here’s an example of testing the ContiguousAllocator
:
describe('ContiguousAllocator', () => { it('should allocate CIDR blocks sequentially', () => { const allocator = new ContiguousAllocator('10.0.0.0/16');
expect(allocator.allocate(24)).toBe('10.0.0.0/24'); expect(allocator.allocate(24)).toBe('10.0.1.0/24'); expect(allocator.allocate(24)).toBe('10.0.2.0/24'); });
it('should throw an error when out of space', () => { const allocator = new ContiguousAllocator('10.0.0.0/24');
expect(allocator.allocate(25)).toBe('10.0.0.0/25'); expect(allocator.allocate(25)).toBe('10.0.0.128/25');
expect(() => allocator.allocate(25)).toThrow(); });});
Performance Considerations
The allocation system is designed to be efficient and performant. Here are some considerations:
- Memory Usage: The allocator stores only the base CIDR, current position, and allocated CIDRs
- Computational Complexity: Allocation operations are O(1) in time complexity
- Scaling: The system can handle thousands of allocations efficiently
For very large configurations, consider breaking them down into smaller configurations or using a streaming approach to process allocations in batches.