Skip to content

Commit

Permalink
Create ClusterIP service through spec
Browse files Browse the repository at this point in the history
Allow users to create a ClusterIP service by setting the ServiceName
field in the spec
  • Loading branch information
jmckulk committed Apr 24, 2024
1 parent 7b6f243 commit 4cba701
Show file tree
Hide file tree
Showing 15 changed files with 306 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,12 @@ spec:
- postgresClusterSelector
type: object
type: array
serviceName:
description: ServiceName will be used as the name of a ClusterIP service
pointing to the pgAdmin pod and port. If the service already exists,
PGO will update the service. For more information about services
reference the Kubernetes and CrunchyData documentation. https://kubernetes.io/docs/concepts/services-networking/service/
type: string
tolerations:
description: 'Tolerations of the PGAdmin pod. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration'
items:
Expand Down
5 changes: 5 additions & 0 deletions internal/controller/standalone_pgadmin/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (r *PGAdminReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&corev1.PersistentVolumeClaim{}).
Owns(&corev1.Secret{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
Watches(
&source.Kind{Type: v1beta1.NewPostgresCluster()},
r.watchPostgresClusters(),
Expand Down Expand Up @@ -121,6 +122,7 @@ func (r *PGAdminReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
configmap *corev1.ConfigMap
dataVolume *corev1.PersistentVolumeClaim
clusters map[string]*v1beta1.PostgresClusterList
_ *corev1.Service
)

_, err = r.reconcilePGAdminSecret(ctx, pgAdmin)
Expand All @@ -134,6 +136,9 @@ func (r *PGAdminReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
if err == nil {
dataVolume, err = r.reconcilePGAdminDataVolume(ctx, pgAdmin)
}
if err == nil {
err = r.reconcilePGAdminService(ctx, pgAdmin)
}
if err == nil {
err = r.reconcilePGAdminStatefulSet(ctx, pgAdmin, configmap, dataVolume)
}
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/standalone_pgadmin/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

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

func TestDeleteControlled(t *testing.T) {
Expand All @@ -36,7 +37,7 @@ func TestDeleteControlled(t *testing.T) {
ns := setupNamespace(t, cc)
reconciler := PGAdminReconciler{Client: cc}

pgadmin := testPGAdmin()
pgadmin := new(v1beta1.PGAdmin)
pgadmin.Namespace = ns.Name
pgadmin.Name = strings.ToLower(t.Name())
assert.NilError(t, cc.Create(ctx, pgadmin))
Expand Down
112 changes: 112 additions & 0 deletions internal/controller/standalone_pgadmin/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2023 - 2024 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/pkg/errors"

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

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

// reconcilePGAdminService will reconcile a ClusterIP service that points to
// pgAdmin.
func (r *PGAdminReconciler) reconcilePGAdminService(
ctx context.Context,
pgadmin *v1beta1.PGAdmin,
) error {
log := logging.FromContext(ctx)

// Since spec.Service only accepts a single service name, we shouldn't ever
// have more than one service. However, if the user changes ServiceName, we
// need to delete any existing service(s). At the start of every reconcile
// get all services that match the current pgAdmin labels.
services := corev1.ServiceList{}
if err := r.Client.List(ctx, &services, client.MatchingLabels{
naming.LabelStandalonePGAdmin: pgadmin.Name,
naming.LabelRole: naming.RolePGAdmin,
}); err != nil {
return err
}

// Delete any controlled and labeled service that is not defined in the spec.
for i := range services.Items {
if services.Items[i].Name != pgadmin.Spec.ServiceName {
log.V(1).Info(
"Deleting service(s) not defined in spec.ServiceName that are owned by pgAdmin",
"serviceName", services.Items[i].Name)
if err := r.deleteControlled(ctx, pgadmin, &services.Items[i]); err != nil {
return err
}
}
}

// TODO (jmckulk): check if the requested services exists without our pgAdmin
// as the owner. If this happens, don't take over ownership of the existing svc.

// At this point only a service defined by spec.ServiceName should exist.
// Update the service or create it if it does not exist
if pgadmin.Spec.ServiceName != "" {
service := service(pgadmin)
if err := errors.WithStack(r.setControllerReference(pgadmin, service)); err != nil {
return err
}
return errors.WithStack(r.apply(ctx, service))
}

// If we get here then ServiceName was not provided through the spec
return nil
}

// Generate a corev1.Service for pgAdmin
func service(pgadmin *v1beta1.PGAdmin) *corev1.Service {

service := &corev1.Service{}
service.ObjectMeta = metav1.ObjectMeta{
Name: pgadmin.Spec.ServiceName,
Namespace: pgadmin.Namespace,
}
service.SetGroupVersionKind(
corev1.SchemeGroupVersion.WithKind("Service"))

service.Annotations = pgadmin.Spec.Metadata.GetAnnotationsOrNil()
service.Labels = naming.Merge(
pgadmin.Spec.Metadata.GetLabelsOrNil(),
naming.StandalonePGAdminLabels(pgadmin.Name))

service.Spec.Type = corev1.ServiceTypeClusterIP
service.Spec.Selector = map[string]string{
naming.LabelStandalonePGAdmin: pgadmin.Name,
}
service.Spec.Ports = []corev1.ServicePort{{
Name: "pgadmin-port",
Port: pgAdminPort,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromInt(pgAdminPort),
}}

return service
}
71 changes: 71 additions & 0 deletions internal/controller/standalone_pgadmin/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2023 - 2024 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/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

func TestService(t *testing.T) {
pgadmin := new(v1beta1.PGAdmin)
pgadmin.Name = "daisy"
pgadmin.Namespace = "daisy-service-ns"
pgadmin.Spec.ServiceName = "daisy-service"
pgadmin.Spec.Metadata = &v1beta1.Metadata{
Labels: map[string]string{
"test-label": "test-label-val",
"postgres-operator.crunchydata.com/pgadmin": "bad-val",
"postgres-operator.crunchydata.com/role": "bad-val",
},
Annotations: map[string]string{
"test-annotation": "test-annotation-val",
},
}

service := service(pgadmin)
assert.Assert(t, service != nil)
assert.Assert(t, cmp.MarshalMatches(service.TypeMeta, `
apiVersion: v1
kind: Service
`))

assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, `
annotations:
test-annotation: test-annotation-val
creationTimestamp: null
labels:
postgres-operator.crunchydata.com/pgadmin: daisy
postgres-operator.crunchydata.com/role: pgadmin
test-label: test-label-val
name: daisy-service
namespace: daisy-service-ns
`))

assert.Assert(t, cmp.MarshalMatches(service.Spec, `
ports:
- name: pgadmin-port
port: 5050
protocol: TCP
targetPort: 5050
selector:
postgres-operator.crunchydata.com/pgadmin: daisy
type: ClusterIP
`))
}
5 changes: 1 addition & 4 deletions internal/controller/standalone_pgadmin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,7 @@ func (r *PGAdminReconciler) writePGAdminUsers(ctx context.Context, pgadmin *v1be
)
intentUserSecret.Labels = naming.Merge(
pgadmin.Spec.Metadata.GetLabelsOrNil(),
map[string]string{
naming.LabelStandalonePGAdmin: pgadmin.Name,
naming.LabelRole: naming.RolePGAdmin,
})
naming.StandalonePGAdminLabels(pgadmin.Name))

// Initialize secret data map, or copy existing data if not nil
intentUserSecret.Data = make(map[string][]byte)
Expand Down
10 changes: 3 additions & 7 deletions internal/controller/standalone_pgadmin/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,15 @@ func (r *PGAdminReconciler) reconcilePGAdminDataVolume(

// pvc defines the data volume for pgAdmin.
func pvc(pgadmin *v1beta1.PGAdmin) *corev1.PersistentVolumeClaim {
labelMap := map[string]string{
naming.LabelStandalonePGAdmin: pgadmin.Name,
naming.LabelRole: naming.RolePGAdmin,
naming.LabelData: naming.DataPGAdmin,
pvc := &corev1.PersistentVolumeClaim{
ObjectMeta: naming.StandalonePGAdmin(pgadmin),
}

pvc := &corev1.PersistentVolumeClaim{ObjectMeta: naming.StandalonePGAdmin(pgadmin)}
pvc.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("PersistentVolumeClaim"))

pvc.Annotations = pgadmin.Spec.Metadata.GetAnnotationsOrNil()
pvc.Labels = naming.Merge(
pgadmin.Spec.Metadata.GetLabelsOrNil(),
labelMap,
naming.StandalonePGAdminDataLabels(pgadmin.Name),
)
pvc.Spec = pgadmin.Spec.DataVolumeClaimSpec

Expand Down
24 changes: 12 additions & 12 deletions internal/naming/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,32 +299,32 @@ func PGBackRestRepoVolumeLabels(clusterName, repoName string) labels.Set {
return labels.Merge(repoLabels, repoVolLabels)
}

// StandalonePGAdminLabels return labels for standalone pgadmin resources
func StandalonePGAdminLabels(pgadminName string) labels.Set {
// StandalonePGAdminLabels return labels for standalone pgAdmin resources
func StandalonePGAdminLabels(pgAdminName string) labels.Set {
return map[string]string{
LabelStandalonePGAdmin: pgadminName,
LabelStandalonePGAdmin: pgAdminName,
LabelRole: RolePGAdmin,
}
}

// StandalonePGAdminSelector provides a selector for standalone pgadmin resources
func StandalonePGAdminSelector(pgadminName string) labels.Selector {
return StandalonePGAdminLabels(pgadminName).AsSelector()
// StandalonePGAdminSelector provides a selector for standalone pgAdmin resources
func StandalonePGAdminSelector(pgAdminName string) labels.Selector {
return StandalonePGAdminLabels(pgAdminName).AsSelector()
}

// StandalonePGAdminDataLabels returns the labels for standalone pgadmin resources
// StandalonePGAdminDataLabels returns the labels for standalone pgAdmin resources
// that contain or mount data
func StandalonePGAdminDataLabels(pgadminName string) labels.Set {
func StandalonePGAdminDataLabels(pgAdminName string) labels.Set {
return labels.Merge(
StandalonePGAdminLabels(pgadminName),
StandalonePGAdminLabels(pgAdminName),
map[string]string{
LabelData: DataPGAdmin,
},
)
}

// StandalonePGAdminDataSelector returns a selector for standalone pgadmin resources
// StandalonePGAdminDataSelector returns a selector for standalone pgAdmin resources
// that contain or mount data
func StandalonePGAdminDataSelector(pgadmiName string) labels.Selector {
return StandalonePGAdminDataLabels(pgadmiName).AsSelector()
func StandalonePGAdminDataSelector(pgAdmiName string) labels.Selector {
return StandalonePGAdminDataLabels(pgAdmiName).AsSelector()
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ type PGAdminSpec struct {
// +listMapKey=username
// +optional
Users []PGAdminUser `json:"users,omitempty"`

// ServiceName will be used as the name of a ClusterIP service pointing
// to the pgAdmin pod and port. If the service already exists, PGO will
// update the service. For more information about services reference
// the Kubernetes and CrunchyData documentation.
// https://kubernetes.io/docs/concepts/services-networking/service/
// +optional
ServiceName string `json:"serviceName,omitempty"`
}

type ServerGroup struct {
Expand Down
13 changes: 13 additions & 0 deletions testing/kuttl/e2e/standalone-pgadmin-service/00--pgadmin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PGAdmin
metadata:
name: pgadmin
spec:
dataVolumeClaimSpec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 1Gi
serverGroups: []
serviceName: pgadmin-service
16 changes: 16 additions & 0 deletions testing/kuttl/e2e/standalone-pgadmin-service/00-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: pgadmin-service
labels:
postgres-operator.crunchydata.com/role: pgadmin
postgres-operator.crunchydata.com/pgadmin: pgadmin
spec:
selector:
postgres-operator.crunchydata.com/pgadmin: pgadmin
ports:
- port: 5050
targetPort: 5050
protocol: TCP
name: pgadmin-port
type: ClusterIP
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PGAdmin
metadata:
name: pgadmin
spec:
dataVolumeClaimSpec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 1Gi
serverGroups: []
serviceName: pgadmin-service-updated
16 changes: 16 additions & 0 deletions testing/kuttl/e2e/standalone-pgadmin-service/01-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: pgadmin-service-updated
labels:
postgres-operator.crunchydata.com/role: pgadmin
postgres-operator.crunchydata.com/pgadmin: pgadmin
spec:
selector:
postgres-operator.crunchydata.com/pgadmin: pgadmin
ports:
- port: 5050
targetPort: 5050
protocol: TCP
name: pgadmin-port
type: ClusterIP

0 comments on commit 4cba701

Please sign in to comment.