Skip to content

Commit

Permalink
Reconcile a pgAdmin StatefulSet, Pod PVC and ConfigMap
Browse files Browse the repository at this point in the history
Add the reconciliation logic for the main initial elements for
pgAdmin. Includes initial configuration options for the StatefulSet
and example implementations for the ConfigMap, PVC and Status block
  • Loading branch information
tjmoore4 committed Oct 3, 2023
1 parent 3a96ba7 commit 4db6117
Show file tree
Hide file tree
Showing 23 changed files with 3,070 additions and 30 deletions.
9 changes: 9 additions & 0 deletions build/crd/pgadmins/todos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,14 @@
- op: copy
from: /work
path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/imagePullSecrets/items/properties/name/description
- op: copy
from: /work
path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/config/properties/files/items/properties/configMap/properties/name/description
- op: copy
from: /work
path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/config/properties/files/items/properties/secret/properties/name/description
- op: copy
from: /work
path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/config/properties/ldapBindPassword/properties/name/description
- op: remove
path: /work
8 changes: 5 additions & 3 deletions cmd/postgres-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/crunchydata/postgres-operator/internal/controller/runtime"
"github.com/crunchydata/postgres-operator/internal/controller/standalone_pgadmin"
"github.com/crunchydata/postgres-operator/internal/logging"
"github.com/crunchydata/postgres-operator/internal/naming"
"github.com/crunchydata/postgres-operator/internal/upgradecheck"
"github.com/crunchydata/postgres-operator/internal/util"
)
Expand Down Expand Up @@ -153,9 +154,10 @@ func addControllersToManager(mgr manager.Manager, openshift bool, log logr.Logge

if util.DefaultMutableFeatureGate.Enabled(util.StandalonePGAdmin) {
pgAdminReconciler := &standalone_pgadmin.PGAdminReconciler{
Client: mgr.GetClient(),
Owner: "pgadmin-controller",
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Owner: "pgadmin-controller",
Recorder: mgr.GetEventRecorderFor(naming.ControllerPGAdmin),
Scheme: mgr.GetScheme(),
}

if err := pgAdminReconciler.SetupWithManager(mgr); err != nil {
Expand Down
527 changes: 527 additions & 0 deletions config/crd/bases/postgres-operator.crunchydata.com_pgadmins.yaml

Large diffs are not rendered by default.

873 changes: 859 additions & 14 deletions docs/content/references/crd.md

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions examples/pgadmin/pgadmin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PGAdmin
metadata:
name: pgadmin
spec: {}
name: rhino
spec:
dataVolumeClaimSpec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 1Gi
57 changes: 57 additions & 0 deletions internal/controller/standalone_pgadmin/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2023 Crunchy Data Solutions, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package standalone_pgadmin

import (
"context"
"reflect"

"sigs.k8s.io/controller-runtime/pkg/client"
)

// patch sends patch to object's endpoint in the Kubernetes API and updates
// object with any returned content. The fieldManager is set to r.Owner, but
// can be overridden in options.
// - https://docs.k8s.io/reference/using-api/server-side-apply/#managers
//
// TODO(tjmoore4): This function is duplicated from a version that takes a PostgresCluster object.
func (r *PGAdminReconciler) patch(
ctx context.Context, object client.Object,
patch client.Patch, options ...client.PatchOption,
) error {
options = append([]client.PatchOption{r.Owner}, options...)
return r.Client.Patch(ctx, object, patch, options...)
}

// apply sends an apply patch to object's endpoint in the Kubernetes API and
// updates object with any returned content. The fieldManager is set to
// r.Owner and the force parameter is true.
// - https://docs.k8s.io/reference/using-api/server-side-apply/#managers
// - https://docs.k8s.io/reference/using-api/server-side-apply/#conflicts
//
// TODO(tjmoore4): This function is duplicated from a version that takes a PostgresCluster object.
func (r *PGAdminReconciler) apply(ctx context.Context, object client.Object) error {
// Generate an apply-patch by comparing the object to its zero value.
zero := reflect.New(reflect.TypeOf(object).Elem()).Interface()
data, err := client.MergeFrom(zero.(client.Object)).Data(object)
apply := client.RawPatch(client.Apply.Type(), data)

// Send the apply-patch with force=true.
if err == nil {
err = r.patch(ctx, object, apply, client.ForceOwnership)
}

return err
}
20 changes: 20 additions & 0 deletions internal/controller/standalone_pgadmin/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2023 Crunchy Data Solutions, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package standalone_pgadmin

const (
// key for standalone pgAdmin settings
settingsConfigMapKey = "pgadmin-settings.json"
)
65 changes: 65 additions & 0 deletions internal/controller/standalone_pgadmin/configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2023 Crunchy Data Solutions, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package standalone_pgadmin

import (
"context"

corev1 "k8s.io/api/core/v1"

"github.com/pkg/errors"

"github.com/crunchydata/postgres-operator/internal/initialize"
"github.com/crunchydata/postgres-operator/internal/naming"
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

// +kubebuilder:rbac:groups="",resources="configmaps",verbs={get}
// +kubebuilder:rbac:groups="",resources="configmaps",verbs={create,delete,patch}

// reconcilePGAdminConfigMap writes the ConfigMap for pgAdmin.
func (r *PGAdminReconciler) reconcilePGAdminConfigMap(
ctx context.Context, pgadmin *v1beta1.PGAdmin,
) (*corev1.ConfigMap, error) {
configmap := configmap(pgadmin)

err := errors.WithStack(r.setControllerReference(pgadmin, configmap))

if err == nil {
err = errors.WithStack(r.apply(ctx, configmap))
}

return configmap, err
}

// configmap returns a v1.ConfigMap for pgAdmin.
func configmap(pgadmin *v1beta1.PGAdmin) *corev1.ConfigMap {
configmap := &corev1.ConfigMap{ObjectMeta: naming.StandalonePGAdmin(pgadmin)}
configmap.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap"))

configmap.Annotations = pgadmin.Spec.Metadata.GetAnnotationsOrNil()
configmap.Labels = naming.Merge(
pgadmin.Spec.Metadata.GetLabelsOrNil(),
map[string]string{
naming.LabelStandalonePGAdmin: pgadmin.Name,
naming.LabelRole: naming.RolePGAdmin,
})

// TODO(tjmoore4): Populate configuration details.
initialize.StringMap(&configmap.Data)
configmap.Data[settingsConfigMapKey] = "config data"

return configmap
}
76 changes: 76 additions & 0 deletions internal/controller/standalone_pgadmin/configmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2023 Crunchy Data Solutions, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package standalone_pgadmin

import (
"testing"

"gotest.tools/v3/assert"

"github.com/crunchydata/postgres-operator/internal/testing/cmp"
"github.com/crunchydata/postgres-operator/internal/testing/require"
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

func TestGeneratePGAdminConfigMap(t *testing.T) {
require.ParallelCapacity(t, 0)

pgadmin := new(v1beta1.PGAdmin)
pgadmin.Namespace = "some-ns"
pgadmin.Name = "pg1"

t.Run("Data,ObjectMeta,TypeMeta", func(t *testing.T) {
pgadmin := pgadmin.DeepCopy()

configmap := configmap(pgadmin)

assert.Assert(t, cmp.MarshalMatches(configmap.TypeMeta, `
apiVersion: v1
kind: ConfigMap
`))
assert.Assert(t, cmp.MarshalMatches(configmap.ObjectMeta, `
creationTimestamp: null
labels:
postgres-operator.crunchydata.com/role: pgadmin
postgres-operator.crunchydata.com/standalone-pgadmin: pg1
name: pg1-standalone-pgadmin
namespace: some-ns
`))

assert.Assert(t, len(configmap.Data) > 0, "expected some configuration")
})

t.Run("Annotations,Labels", func(t *testing.T) {
pgadmin := pgadmin.DeepCopy()
pgadmin.Spec.Metadata = &v1beta1.Metadata{
Annotations: map[string]string{"a": "v1", "b": "v2"},
Labels: map[string]string{"c": "v3", "d": "v4"},
}

configmap := configmap(pgadmin)

// Annotations present in the metadata.
assert.DeepEqual(t, configmap.ObjectMeta.Annotations, map[string]string{
"a": "v1", "b": "v2",
})

// Labels present in the metadata.
assert.DeepEqual(t, configmap.ObjectMeta.Labels, map[string]string{
"c": "v3", "d": "v4",
"postgres-operator.crunchydata.com/standalone-pgadmin": "pg1",
"postgres-operator.crunchydata.com/role": "pgadmin",
})
})
}
74 changes: 67 additions & 7 deletions internal/controller/standalone_pgadmin/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ package standalone_pgadmin
import (
"context"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"github.com/crunchydata/postgres-operator/internal/logging"
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
Expand All @@ -28,8 +32,9 @@ import (
// PGAdminReconciler reconciles a PGAdmin object
type PGAdminReconciler struct {
client.Client
Owner client.FieldOwner
Scheme *runtime.Scheme
Owner client.FieldOwner
Recorder record.EventRecorder
Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=postgres-operator.crunchydata.com,resources=pgadmins,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -40,22 +45,77 @@ type PGAdminReconciler struct {
// desired state described in a [v1beta1.PGAdmin] identified by request.
func (r *PGAdminReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

var err error
log := logging.FromContext(ctx)

pgAdmin := &v1beta1.PGAdmin{}
if err := r.Get(ctx, req.NamespacedName, pgAdmin); err != nil {
if err = client.IgnoreNotFound(err); err != nil {
log.Error(err, "unable to fetch PGAdmin")
// NotFound cannot be fixed by requeuing so ignore it. During background
// deletion, we receive delete events from pgadmin's dependents after
// pgadmin is deleted.
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Write any changes to the pgadmin status on the way out.
before := pgAdmin.DeepCopy()
defer func() {
if !equality.Semantic.DeepEqual(before.Status, pgAdmin.Status) {
statusErr := r.Status().Patch(ctx, pgAdmin, client.MergeFrom(before), r.Owner)
if statusErr != nil {
log.Error(statusErr, "Patching PGAdmin status")
}
if err == nil {
err = statusErr
}
}
return ctrl.Result{}, err
}()

var configmap *corev1.ConfigMap
var dataVolume *corev1.PersistentVolumeClaim

if err == nil {
configmap, err = r.reconcilePGAdminConfigMap(ctx, pgAdmin)
}
if err == nil {
dataVolume, err = r.reconcilePGAdminDataVolume(ctx, pgAdmin)
}
if err == nil {
err = r.reconcilePGAdminStatefulSet(ctx, pgAdmin, configmap, dataVolume)
}

if err == nil {
// at this point everything reconciled successfully, and we can update the
// observedGeneration
pgAdmin.Status.ObservedGeneration = pgAdmin.GetGeneration()
log.V(1).Info("reconciled cluster")
}
log.Info("Reconciling pgAdmin")
return ctrl.Result{}, nil

return ctrl.Result{}, err
}

// SetupWithManager sets up the controller with the Manager.
//
// TODO(tjmoore4): This function is duplicated from a version that takes a PostgresCluster object.
func (r *PGAdminReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1beta1.PGAdmin{}).
Complete(r)
}

// The owner reference created by controllerutil.SetControllerReference blocks
// deletion. The OwnerReferencesPermissionEnforcement plugin requires that the
// creator of such a reference have either "delete" permission on the owner or
// "update" permission on the owner's "finalizers" subresource.
// - https://docs.k8s.io/reference/access-authn-authz/admission-controllers/
// +kubebuilder:rbac:groups="postgres-operator.crunchydata.com",resources="pgadmins/finalizers",verbs={update}

// setControllerReference sets owner as a Controller OwnerReference on controlled.
// Only one OwnerReference can be a controller, so it returns an error if another
// is already set.
//
// TODO(tjmoore4): This function is duplicated from a version that takes a PostgresCluster object.
func (r *PGAdminReconciler) setControllerReference(
owner *v1beta1.PGAdmin, controlled client.Object,
) error {
return controllerutil.SetControllerReference(owner, controlled, r.Client.Scheme())
}
Loading

0 comments on commit 4db6117

Please sign in to comment.