| 
					
				 | 
			
			
				@@ -19,17 +19,20 @@ package controller 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	"context" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	databasev1 "github.com/iwanhae/nodb/api/v1" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	"github.com/iwanhae/nodb/internal/templates" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	"github.com/pkg/errors" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	corev1 "k8s.io/api/core/v1" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	apierrors "k8s.io/apimachinery/pkg/api/errors" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	"k8s.io/apimachinery/pkg/api/resource" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	"k8s.io/apimachinery/pkg/runtime" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	"k8s.io/apimachinery/pkg/types" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	"k8s.io/apimachinery/pkg/util/intstr" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	ctrl "sigs.k8s.io/controller-runtime" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	"sigs.k8s.io/controller-runtime/pkg/client" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	"sigs.k8s.io/controller-runtime/pkg/log" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	databasev1 "github.com/iwanhae/nodb/api/v1" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	"github.com/iwanhae/nodb/internal/templates" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	"github.com/pkg/errors" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	corev1 "k8s.io/api/core/v1" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 // PostgreSQLReconciler reconciles a PostgreSQL object 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -52,7 +55,7 @@ type PostgreSQLReconciler struct { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 // 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 // For more details, check Reconcile and its Result here: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.0/pkg/reconcile 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-func (r *PostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+func (r *PostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	logger := log.FromContext(ctx) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	obj := databasev1.PostgreSQL{} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -63,20 +66,91 @@ func (r *PostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Request) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		logger.Error(err, "resource not found") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		return ctrl.Result{}, err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	original := obj.DeepCopy() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	logger.Info("reconcile", "namespace", obj.Namespace, "name", obj.Name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// Pending: Creating Pod if not exists 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// do not handle spec update 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	defer func() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		status := databasev1.Status_Ready 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if obj.Status.Conditions.Pod.IsLowerThan(status) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			status = obj.Status.Conditions.Pod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if obj.Status.Conditions.Service.IsLowerThan(status) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			status = obj.Status.Conditions.Service 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		obj.Status.Status = status 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		err = r.Status().Patch(ctx, &obj, client.MergeFrom(original)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			logger.Error(err, "failed to update status") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	}() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	if err := r.createOrUpdatePod(ctx, &obj); err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return ctrl.Result{}, err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	if err := r.createOrUpdateService(ctx, &obj); err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return ctrl.Result{}, err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	return ctrl.Result{}, nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+func (r *PostgreSQLReconciler) createOrUpdateService(ctx context.Context, obj *databasev1.PostgreSQL) error { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	logger := log.FromContext(ctx) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	svc := corev1.Service{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		ObjectMeta: metav1.ObjectMeta{Name: obj.Name, Namespace: obj.Namespace}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	err := r.Client.Get(ctx, types.NamespacedName{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		Namespace: obj.Namespace, Name: obj.Name, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	}, &svc) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	if err != nil && !apierrors.IsNotFound(err) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// can't handle other than not found error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	logger.Info("create or update service") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	if result, err := controllerutil.CreateOrUpdate(ctx, r.Client, &svc, func() error { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if err := controllerutil.SetOwnerReference(obj, &svc, r.Scheme); err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			return errors.Wrap(err, "failed to set owner reference") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		svc.ObjectMeta.Labels = map[string]string{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			templates.LabelKeyType: templates.LabelValuePostgreSQL, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			templates.LabelKeyName: obj.Name, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		svc.Spec.Selector = map[string]string{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			templates.LabelKeyType: templates.LabelValuePostgreSQL, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			templates.LabelKeyName: obj.Name, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		svc.Spec.Ports = []corev1.ServicePort{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{Name: "postgres", Port: 5432, TargetPort: intstr.FromString("postgres"), Protocol: corev1.ProtocolTCP}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		svc.Spec.Type = corev1.ServiceTypeNodePort 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		svc.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyLocal 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	}); err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} else if result != controllerutil.OperationResultNone { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		logger.Info("service modified", "result", result) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	obj.Status.Conditions.Service = databasev1.Status_Ready 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	return nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+func (r *PostgreSQLReconciler) createOrUpdatePod(ctx context.Context, obj *databasev1.PostgreSQL) error { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	logger := log.FromContext(ctx) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	pod := corev1.Pod{} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	if err := r.Client.Get(ctx, req.NamespacedName, &pod); err == nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	if err := r.Client.Get(ctx, types.NamespacedName{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		Namespace: obj.Namespace, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		Name:      obj.Name, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	}, &pod); err == nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		// if found return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		logger.Info("ignore already existing pod") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		return ctrl.Result{}, nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} else if !apierrors.IsNotFound(err) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		// weird error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		return ctrl.Result{}, err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// can't handle other than not found error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return err 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	pod = templates.PostgreSQLPod(templates.PostgreSQLOpts{ 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -89,21 +163,16 @@ func (r *PostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Request) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		Database: obj.Spec.Database, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		Memory: resource.MustParse("1Gi"), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		Owner:  &obj, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		Owner:  obj, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	logger.Info("create pod", "namespace", pod.Namespace, "name", pod.Name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	if err := r.Client.Create(ctx, &pod); err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		return ctrl.Result{}, errors.Wrap(err, "failed to create pod") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return errors.Wrap(err, "failed to create pod") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	obj.Status.Status = databasev1.Status_Initializing 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	logger.Info("update status") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	if err := r.Status().Update(ctx, &obj); err != nil { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		return ctrl.Result{}, errors.Wrap(err, "failed to patch status") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	return ctrl.Result{}, nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	obj.Status.Conditions.Pod = databasev1.Status_Initializing 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	return nil 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 // SetupWithManager sets up the controller with the Manager. 
			 |