Files
zeekayandHanzo Dev 7cbe76b386 Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers, and int32Ptr went once new(expr) could say it; omitzero is not
applied, since it changes what the JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:07:47 -07:00

259 lines
8.4 KiB
Go

package controller
import (
"context"
"fmt"
"maps"
"github.com/go-logr/logr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/hanzoai/nchain/api/v1alpha1"
"github.com/hanzoai/nchain/internal/status"
)
// NetworkReconciler reconciles a Network object. It is the top-level composer
// that creates and manages all child CRDs (NodeClusters, Chains, Indexers,
// Explorers, Bridges, Gateways).
type NetworkReconciler struct {
client.Client
Scheme *runtime.Scheme
Log logr.Logger
}
// +kubebuilder:rbac:groups=nchain.hanzo.ai,resources=networks,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=nchain.hanzo.ai,resources=networks/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=nchain.hanzo.ai,resources=nodeclusters;chains;indexers;explorers;bridges;gateways;clouds,verbs=get;list;watch;create;update;patch;delete
func (r *NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("network", req.NamespacedName)
var network v1alpha1.Network
if err := r.Get(ctx, req.NamespacedName, &network); err != nil {
if apierrors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
// Reconcile child NodeClusters.
for _, named := range network.Spec.Clusters {
child := &v1alpha1.NodeCluster{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, named.Name, "nodecluster"),
Spec: named.Spec,
}
// Inherit protocol and networkID if not set.
if child.Spec.Protocol == "" {
child.Spec.Protocol = network.Spec.Protocol
}
if child.Spec.NetworkID == "" {
child.Spec.NetworkID = network.Spec.NetworkID
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Reconcile child Chains.
for _, named := range network.Spec.Chains {
child := &v1alpha1.Chain{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, named.Name, "chain"),
Spec: named.Spec,
}
if child.Spec.Protocol == "" {
child.Spec.Protocol = network.Spec.Protocol
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Reconcile child Indexers.
for _, named := range network.Spec.Indexers {
child := &v1alpha1.Indexer{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, named.Name, "indexer"),
Spec: named.Spec,
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Reconcile child Explorers.
for _, named := range network.Spec.Explorers {
child := &v1alpha1.Explorer{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, named.Name, "explorer"),
Spec: named.Spec,
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Reconcile child Bridges.
for _, named := range network.Spec.Bridges {
child := &v1alpha1.Bridge{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, named.Name, "bridge"),
Spec: named.Spec,
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Reconcile child Gateways.
for _, named := range network.Spec.Gateways {
child := &v1alpha1.Gateway{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, named.Name, "gateway"),
Spec: named.Spec,
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Reconcile Cloud child CR if spec.cloud is set.
if network.Spec.Cloud != nil && network.Spec.Cloud.Enabled {
child := &v1alpha1.Cloud{
Name: network.Name + "-cloud",
Namespace: network.Namespace,
Labels: mergeWithNetworkLabels(&network, "cloud", "cloud"),
}
// Map inline NetworkCloudSpec to standalone CloudSpec.
if network.Spec.Cloud.APIImage != nil {
child.Spec.API.Image = network.Spec.Cloud.APIImage
}
if network.Spec.Cloud.WebImage != nil {
child.Spec.Web.Image = network.Spec.Cloud.WebImage
}
if network.Spec.Cloud.Replicas != nil {
child.Spec.API.Replicas = network.Spec.Cloud.Replicas
}
if network.Spec.Cloud.Ingress != nil {
child.Spec.Ingress = network.Spec.Cloud.Ingress
}
if network.Spec.Cloud.Database != nil && network.Spec.Cloud.Database.CredentialsSecret != "" {
child.Spec.Database.CredentialsSecret = network.Spec.Cloud.Database.CredentialsSecret
}
if err := r.reconcileChild(ctx, &network, child); err != nil {
return ctrl.Result{}, err
}
}
// Aggregate status from children.
network.Status.ObservedGeneration = network.Generation
network.Status.ClusterCount = int32(len(network.Spec.Clusters))
network.Status.ChainCount = int32(len(network.Spec.Chains))
// Count ready clusters.
var readyClusters int32
for _, named := range network.Spec.Clusters {
var child v1alpha1.NodeCluster
key := client.ObjectKey{
Name: fmt.Sprintf("%s-%s", network.Name, named.Name),
Namespace: network.Namespace,
}
if err := r.Get(ctx, key, &child); err == nil {
if child.Status.Phase == v1alpha1.PhaseRunning {
readyClusters++
}
}
}
network.Status.ReadyClusters = readyClusters
// Determine overall phase.
if readyClusters == network.Status.ClusterCount && network.Status.ClusterCount > 0 {
network.Status.Phase = v1alpha1.PhaseRunning
status.SetCondition(&network.Status.Conditions, v1alpha1.ConditionTypeReady,
metav1.ConditionTrue, "AllClustersReady", "All node clusters are running")
} else if readyClusters > 0 {
network.Status.Phase = v1alpha1.PhaseBootstrapping
status.SetCondition(&network.Status.Conditions, v1alpha1.ConditionTypeReady,
metav1.ConditionFalse, "PartiallyReady",
fmt.Sprintf("%d/%d clusters ready", readyClusters, network.Status.ClusterCount))
} else {
network.Status.Phase = v1alpha1.PhaseCreating
status.SetCondition(&network.Status.Conditions, v1alpha1.ConditionTypeReady,
metav1.ConditionFalse, "Creating", "Network resources are being created")
}
if err := r.Status().Update(ctx, &network); err != nil {
return ctrl.Result{}, err
}
log.Info("Reconciled Network", "phase", network.Status.Phase,
"clusters", network.Status.ClusterCount, "ready", readyClusters)
return ctrl.Result{}, nil
}
// reconcileChild creates or updates a child resource with owner reference.
func (r *NetworkReconciler) reconcileChild(ctx context.Context, network *v1alpha1.Network, child client.Object) error {
if err := ctrl.SetControllerReference(network, child, r.Scheme); err != nil {
return err
}
existing := child.DeepCopyObject().(client.Object)
err := r.Get(ctx, client.ObjectKeyFromObject(child), existing)
if apierrors.IsNotFound(err) {
return r.Create(ctx, child)
}
if err != nil {
return err
}
// Merge labels.
existingLabels := existing.GetLabels()
if existingLabels == nil {
existingLabels = make(map[string]string)
}
maps.Copy(existingLabels, child.GetLabels())
existing.SetLabels(existingLabels)
return r.Update(ctx, existing)
}
// mergeWithNetworkLabels builds labels for a network child resource.
func mergeWithNetworkLabels(network *v1alpha1.Network, name, component string) map[string]string {
labels := map[string]string{
"app.kubernetes.io/name": name,
"app.kubernetes.io/instance": name,
"app.kubernetes.io/component": component,
"app.kubernetes.io/part-of": network.Name,
"app.kubernetes.io/managed-by": "nchain-operator",
"nchain.hanzo.ai/network": network.Name,
}
maps.Copy(labels, network.Spec.Labels)
return labels
}
func (r *NetworkReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.Network{}).
Owns(&v1alpha1.NodeCluster{}).
Owns(&v1alpha1.Chain{}).
Owns(&v1alpha1.Indexer{}).
Owns(&v1alpha1.Explorer{}).
Owns(&v1alpha1.Bridge{}).
Owns(&v1alpha1.Gateway{}).
Owns(&v1alpha1.Cloud{}).
WithEventFilter(specChangePred()).
Complete(r)
}