The CRIB SDK is an Infrastructure-as-Code (IaC) SDK for applications, designed to simplify the deployment and management of Chainlink nodes and other Kubernetes resources. It provides a component-based architecture that allows developers to define reusable components and deployment plans.
The crib-sdk provides a structured way to define and deploy Kubernetes applications using a component-based approach. It uses CDK8s (Cloud Development Kit for Kubernetes) under the hood to synthesize Kubernetes manifests from high-level constructs.
# Clone the repository
git clone https://github.com/smartcontractkit/crib-sdk.git
cd crib-sdk
# Install dependencies
go mod download
# Install supporting tools
task install:tools
# Build cribctl
task build:cribctl
# You can now use cribctl to apply plans
.build/cribctl plan apply example
The SDK organizes components into two main categories:
Located in crib/scalar/, these are the basic building blocks of the SDK. Each scalar component:
- Lives in its own versioned package (e.g.,
configmap/v1
,namespace/v1
) - Represents a single Kubernetes resource or simple operation
- Should be simple, composable, and reusable
- Does not include other components
Examples:
- ConfigMap: Creates Kubernetes ConfigMap resources
- Namespace: Creates Kubernetes Namespace resources
- HelmChart: Deploys Helm charts
- ClientSideApply: Executes client-side operations
- RemoteApply: Fetches and applies remote manifests
Located in crib/composite/, these are higher-level components that:
- Combine multiple scalar components or other resources
- Provide more complex functionality
- Can include dependencies and ordering logic
Example:
- NginxController: Fetches NGINX Ingress Controller manifests, creates namespace, applies manifests, and waits for pods to be ready
Plans are the core abstraction for defining deployment strategies:
- Represent a Directed Acyclic Graph (DAG) of components and child plans
- Support hierarchical composition through child plans
- Include cycle detection to prevent circular dependencies
- Are built lazily during execution
Key concepts:
- Components: List of component functions to execute
- ChildPlans: Dependent plans that must be resolved
- Namespace: Primary target namespace for the plan
- Build(): Resolves the DAG and returns a plan with resolved dependencies
graph TD
A[User Creates Plan] --> B[NewPlan with Components]
B --> C[Add Child Plans via AddPlan]
C --> D[plan.Build called]
D --> E{Cycle Detection}
E -->|Cycle Found| F[Panic with Cycle Info]
E -->|No Cycle| G[Lazy DFS Traversal]
G --> H[Resolve Child Functions]
H --> I[Mark as Visited]
I --> J[Return Built Plan]
J --> K[PlanService.CreatePlan]
K --> L[Create CDK8s App]
L --> M[Resolve Components]
M --> N[Synthesize Manifests]
N --> O[Write to Temp Directory]
O --> P[ApplyPlan]
P --> Q[Discover Manifests]
Q --> R[Normalize into Bundles]
R --> S[Apply Each Bundle]
S --> T{Is ClientSideApply?}
T -->|Yes| U[Execute Local Command]
T -->|No| V[kubectl apply]
The Chainlink Node composite component (crib/composite/chainlink/node/v1
) implements deterministic configuration merging to ensure consistent behavior across deployments:
- ConfigOverrides: Additional configuration files are processed in lexicographic order by filename
- SecretsOverrides: Additional secrets files are processed in lexicographic order by filename
This sorting ensures that when Chainlink performs configuration merging, the same configuration files are applied in the same order regardless of map iteration order, providing predictable and reproducible deployments.
// Create a plan with namespace component
plan := crib.NewPlan("my-app",
crib.Namespace("my-namespace"),
crib.ComponentSet(
namespace.Component(&namespace.Props{
Namespace: "my-namespace",
}),
),
)
All components follow a similar pattern:
// In scalar/[component]/v1/[component].go
func Component(props *Props) crib.ComponentFunc {
return func(ctx context.Context) (crib.Component, error) {
// Validate props
if err := props.Validate(ctx); err != nil {
return nil, err
}
// Create the component
return New(ctx, props)
}
}
// Create plan service
svc, err := service.NewPlanService(ctx)
// Create app plan
appPlan, err := svc.CreatePlan(ctx, plan)
// Apply the plan
err = appPlan.Apply(ctx)
The runtime discovers manifests in a specific order:
- Scans synthesized manifests in temp directory
- Groups manifests by directory
- Separates ClientSideApply manifests from regular manifests
- Applies bundles in order
- Go 1.24.0 or later
- Task (https://taskfile.dev)
- asdf
- Pre-commit (optional but recommended for code quality) -
task install:pre-commit
- Docker (for some operations)
# Build cribctl specifically
task build:cribctl
# Run all tests
task go:test
# Run lint rules
task go:lint
# Format code
task go:fmt
The project includes a documentation site:
# Start the documentation server.
$ task docker:compose:up
π Service godoc is available at http://localhost:2152.
# Stop the documentation server.
$ task docker:compose:down
This project uses pre-commit hooks to ensure code quality. Install them before making changes:
# Install pre-commit
task install:tools # recommended
# or
pip install pre-commit
# or
brew install pre-commit
# Install the git hooks
task install:pre-commit # recommended
# or
pre-commit install
# Run pre-commit on all files
task pre-commit # recommended
# or
pre-commit run --all-files
The pre-commit configuration includes:
- YAML formatting and linting
- Go linting (golangci-lint)
- JSON formatting
- Spell checking (codespell)
- GitHub Actions linting
- File formatting (end of file, trailing whitespace)
- Scalar Components: Place new scalar components in
crib/scalar/[component]/v1/
- Composite Components: Place new composite components in
crib/composite/[component]/v1/
- Tests: Include unit tests in the same package with
_test.go
suffix - Golden Files: Place test fixtures in
testdata/
directories
Get familiar with CRIB Component best practices
Follow a <noun> <verb>
pattern for CLI subcommands:
- Noun: Represents the resource or action (e.g.,
plan
) - Verb: Represents the action to perform (e.g.,
apply
,create
,delete
) - Use singular nouns for specific resources (e.g.,
plan apply
) - Use plural nouns for collections (e.g.,
plans list
)
- Prefer kebab-case for flags over snake_case (e.g.,
--config-file
instead of--config_file
) - Make the default the right thing for most users
- Write unit tests for all new components
- Include golden file tests for manifest generation
- Use the provided testing utilities in
internal/testing_utils.go
- E2E tests that don't fit in a package go in the
tests/
directory
- Make atomic commits with clear messages
- Reference issues when applicable
- Run pre-commit hooks before pushing
- Fork the repository
- Create a feature branch from
main
- Make your changes following the guidelines above
- Ensure all tests pass and pre-commit hooks are satisfied
- Update documentation if needed
- Submit a pull request with a clear description
This project is licensed under the terms specified in the LICENSE file.