postgresql_controller.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /*
  2. Copyright 2023.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package controller
  14. import (
  15. "context"
  16. databasev1 "github.com/iwanhae/nodb/api/v1"
  17. "github.com/iwanhae/nodb/internal/templates"
  18. "github.com/pkg/errors"
  19. corev1 "k8s.io/api/core/v1"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/types"
  25. "k8s.io/apimachinery/pkg/util/intstr"
  26. ctrl "sigs.k8s.io/controller-runtime"
  27. "sigs.k8s.io/controller-runtime/pkg/client"
  28. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  29. "sigs.k8s.io/controller-runtime/pkg/log"
  30. )
  31. // PostgreSQLReconciler reconciles a PostgreSQL object
  32. type PostgreSQLReconciler struct {
  33. client.Client
  34. Scheme *runtime.Scheme
  35. }
  36. //+kubebuilder:rbac:groups=database.iwanhae.kr,resources=postgresqls,verbs=get;list;watch;create;update;patch;delete
  37. //+kubebuilder:rbac:groups=database.iwanhae.kr,resources=postgresqls/status,verbs=get;update;patch
  38. //+kubebuilder:rbac:groups=database.iwanhae.kr,resources=postgresqls/finalizers,verbs=update
  39. //+kubebuilder:rbac:groups="",resources=pods,verbs=get;create
  40. // Reconcile is part of the main kubernetes reconciliation loop which aims to
  41. // move the current state of the cluster closer to the desired state.
  42. // TODO(user): Modify the Reconcile function to compare the state specified by
  43. // the PostgreSQL object against the actual cluster state, and then
  44. // perform operations to make the cluster state reflect the state specified by
  45. // the user.
  46. //
  47. // For more details, check Reconcile and its Result here:
  48. // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.0/pkg/reconcile
  49. func (r *PostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
  50. logger := log.FromContext(ctx)
  51. obj := databasev1.PostgreSQL{}
  52. if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
  53. if client.IgnoreNotFound(err) == nil {
  54. return ctrl.Result{}, nil
  55. }
  56. logger.Error(err, "resource not found")
  57. return ctrl.Result{}, err
  58. }
  59. original := obj.DeepCopy()
  60. logger.Info("reconcile", "namespace", obj.Namespace, "name", obj.Name)
  61. defer func() {
  62. status := databasev1.Status_Ready
  63. if obj.Status.Conditions.Pod.IsLowerThan(status) {
  64. status = obj.Status.Conditions.Pod
  65. }
  66. if obj.Status.Conditions.Service.IsLowerThan(status) {
  67. status = obj.Status.Conditions.Service
  68. }
  69. obj.Status.Status = status
  70. err = r.Status().Patch(ctx, &obj, client.MergeFrom(original))
  71. if err != nil {
  72. logger.Error(err, "failed to update status")
  73. }
  74. }()
  75. if err := r.createOrUpdatePod(ctx, &obj); err != nil {
  76. return ctrl.Result{}, err
  77. }
  78. if err := r.createOrUpdateService(ctx, &obj); err != nil {
  79. return ctrl.Result{}, err
  80. }
  81. return ctrl.Result{}, nil
  82. }
  83. func (r *PostgreSQLReconciler) createOrUpdateService(ctx context.Context, obj *databasev1.PostgreSQL) error {
  84. logger := log.FromContext(ctx)
  85. svc := corev1.Service{
  86. ObjectMeta: metav1.ObjectMeta{Name: obj.Name, Namespace: obj.Namespace},
  87. }
  88. err := r.Client.Get(ctx, types.NamespacedName{
  89. Namespace: obj.Namespace, Name: obj.Name,
  90. }, &svc)
  91. if err != nil && !apierrors.IsNotFound(err) {
  92. // can't handle other than not found error
  93. return err
  94. }
  95. logger.Info("create or update service")
  96. if result, err := controllerutil.CreateOrUpdate(ctx, r.Client, &svc, func() error {
  97. if err := controllerutil.SetOwnerReference(obj, &svc, r.Scheme); err != nil {
  98. return errors.Wrap(err, "failed to set owner reference")
  99. }
  100. svc.ObjectMeta.Labels = map[string]string{
  101. templates.LabelKeyType: templates.LabelValuePostgreSQL,
  102. templates.LabelKeyName: obj.Name,
  103. }
  104. svc.Spec.Selector = map[string]string{
  105. templates.LabelKeyType: templates.LabelValuePostgreSQL,
  106. templates.LabelKeyName: obj.Name,
  107. }
  108. svc.Spec.Ports = []corev1.ServicePort{
  109. {Name: "postgres", Port: 5432, TargetPort: intstr.FromString("postgres"), Protocol: corev1.ProtocolTCP},
  110. }
  111. svc.Spec.Type = corev1.ServiceTypeNodePort
  112. svc.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyLocal
  113. return nil
  114. }); err != nil {
  115. return err
  116. } else if result != controllerutil.OperationResultNone {
  117. logger.Info("service modified", "result", result)
  118. }
  119. obj.Status.Conditions.Service = databasev1.Status_Ready
  120. return nil
  121. }
  122. func (r *PostgreSQLReconciler) createOrUpdatePod(ctx context.Context, obj *databasev1.PostgreSQL) error {
  123. logger := log.FromContext(ctx)
  124. pod := corev1.Pod{}
  125. if err := r.Client.Get(ctx, types.NamespacedName{
  126. Namespace: obj.Namespace,
  127. Name: obj.Name,
  128. }, &pod); err == nil {
  129. // if found return
  130. logger.Info("ignore already existing pod")
  131. return nil
  132. } else if !apierrors.IsNotFound(err) {
  133. // can't handle other than not found error
  134. return err
  135. }
  136. pod = templates.PostgreSQLPod(templates.PostgreSQLOpts{
  137. Name: obj.Name,
  138. Namespace: obj.Namespace,
  139. Tag: obj.Spec.Version,
  140. User: obj.Spec.User,
  141. Password: obj.Spec.Password,
  142. Database: obj.Spec.Database,
  143. Memory: resource.MustParse("1Gi"),
  144. Owner: obj,
  145. })
  146. logger.Info("create pod", "namespace", pod.Namespace, "name", pod.Name)
  147. if err := r.Client.Create(ctx, &pod); err != nil {
  148. return errors.Wrap(err, "failed to create pod")
  149. }
  150. obj.Status.Conditions.Pod = databasev1.Status_Initializing
  151. return nil
  152. }
  153. // SetupWithManager sets up the controller with the Manager.
  154. func (r *PostgreSQLReconciler) SetupWithManager(mgr ctrl.Manager) error {
  155. return ctrl.NewControllerManagedBy(mgr).
  156. For(&databasev1.PostgreSQL{}).
  157. Complete(r)
  158. }