Skip to content

Commit 019317f

Browse files
nmarukovichhors
andauthored
K8SPG-594 delete custom extensions from installed (#967)
* K8SPG-594 delete custom extensions from installed * update extensions check * fix checks * fix checks * delete unused * fix PR coments * fix PR comments * fix PR * delete logs * update conditions * update annotations adding * use status instead of annotations * fix PR * delete unused logs * fix PR comments * fix PR comments * fix PR comments * fix test * fix the test * fix vulnarabilities * update test * fix logging * fix test * fix PR comments * rename package with common functions * fix imports --------- Co-authored-by: Viacheslav Sarzhan <[email protected]>
1 parent 2414443 commit 019317f

File tree

14 files changed

+187
-36
lines changed

14 files changed

+187
-36
lines changed

Diff for: build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -17511,6 +17511,10 @@ spec:
1751117511
properties:
1751217512
host:
1751317513
type: string
17514+
installedCustomExtensions:
17515+
items:
17516+
type: string
17517+
type: array
1751417518
pgbouncer:
1751517519
properties:
1751617520
ready:

Diff for: build/postgres-operator/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ ARG GO_LDFLAGS
1111
ARG GOOS=linux
1212
ARG TARGETARCH
1313
ARG OPERATOR_CGO_ENABLED=1
14-
ARG EXTENSION_INSTALLER_CGO_ENABLED=0
14+
ARG EXTENSION_INSTALLER_CGO_ENABLED=1
1515
ARG TARGETPLATFORM
1616
ARG BUILDPLATFORM
1717

Diff for: config/crd/bases/pgv2.percona.com_perconapgclusters.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -17917,6 +17917,10 @@ spec:
1791717917
properties:
1791817918
host:
1791917919
type: string
17920+
installedCustomExtensions:
17921+
items:
17922+
type: string
17923+
type: array
1792017924
pgbouncer:
1792117925
properties:
1792217926
ready:

Diff for: deploy/bundle.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -18210,6 +18210,10 @@ spec:
1821018210
properties:
1821118211
host:
1821218212
type: string
18213+
installedCustomExtensions:
18214+
items:
18215+
type: string
18216+
type: array
1821318217
pgbouncer:
1821418218
properties:
1821518219
ready:

Diff for: deploy/crd.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -18210,6 +18210,10 @@ spec:
1821018210
properties:
1821118211
host:
1821218212
type: string
18213+
installedCustomExtensions:
18214+
items:
18215+
type: string
18216+
type: array
1821318217
pgbouncer:
1821418218
properties:
1821518219
ready:

Diff for: deploy/cw-bundle.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -18210,6 +18210,10 @@ spec:
1821018210
properties:
1821118211
host:
1821218212
type: string
18213+
installedCustomExtensions:
18214+
items:
18215+
type: string
18216+
type: array
1821318217
pgbouncer:
1821418218
properties:
1821518219
ready:

Diff for: e2e-tests/tests/custom-extensions/09-assert.yaml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: kuttl.dev/v1beta1
2+
kind: TestAssert
3+
timeout: 30
4+
---
5+
kind: ConfigMap
6+
apiVersion: v1
7+
metadata:
8+
name: 11-check-extensions
9+
data:
10+
data: |2-
11+
pg_stat_monitor
12+
pgaudit
13+
plpgsql
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: kuttl.dev/v1beta1
2+
kind: TestStep
3+
timeout: 30
4+
commands:
5+
- script: |-
6+
set -o errexit
7+
set -o xtrace
8+
9+
source ../../functions
10+
11+
data=$(kubectl -n ${NAMESPACE} exec $(get_client_pod) -- psql -v ON_ERROR_STOP=1 -t -q postgres://postgres:$(get_psql_user_pass custom-extensions-pguser-postgres)@$(get_psql_user_host custom-extensions-pguser-postgres) -c "\c postgres" -c "select extname from pg_extension order by extname")
12+
13+
kubectl create configmap -n "${NAMESPACE}" 11-check-extensions --from-literal=data="${data}"

Diff for: percona/controller/pgcluster/controller.go

+85-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/md5"
66
"fmt"
7+
"io"
78
"reflect"
89
"strings"
910
"time"
@@ -29,13 +30,16 @@ import (
2930
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3031
"sigs.k8s.io/controller-runtime/pkg/source"
3132

33+
"github.com/percona/percona-postgresql-operator/internal/controller/runtime"
3234
"github.com/percona/percona-postgresql-operator/internal/logging"
3335
"github.com/percona/percona-postgresql-operator/internal/naming"
36+
"github.com/percona/percona-postgresql-operator/internal/postgres"
3437
perconaController "github.com/percona/percona-postgresql-operator/percona/controller"
3538
"github.com/percona/percona-postgresql-operator/percona/extensions"
3639
"github.com/percona/percona-postgresql-operator/percona/k8s"
3740
pNaming "github.com/percona/percona-postgresql-operator/percona/naming"
3841
"github.com/percona/percona-postgresql-operator/percona/pmm"
42+
perconaPG "github.com/percona/percona-postgresql-operator/percona/postgres"
3943
"github.com/percona/percona-postgresql-operator/percona/utils/registry"
4044
"github.com/percona/percona-postgresql-operator/percona/watcher"
4145
v2 "github.com/percona/percona-postgresql-operator/pkg/apis/pgv2.percona.com/v2"
@@ -49,8 +53,12 @@ const (
4953

5054
// Reconciler holds resources for the PerconaPGCluster reconciler
5155
type PGClusterReconciler struct {
52-
Client client.Client
53-
Owner client.FieldOwner
56+
Client client.Client
57+
Owner client.FieldOwner
58+
PodExec func(
59+
ctx context.Context, namespace, pod, container string,
60+
stdin io.Reader, stdout, stderr io.Writer, command ...string,
61+
) error
5462
Recorder record.EventRecorder
5563
Tracer trace.Tracer
5664
Platform string
@@ -65,6 +73,13 @@ type PGClusterReconciler struct {
6573

6674
// SetupWithManager adds the PerconaPGCluster controller to the provided runtime manager
6775
func (r *PGClusterReconciler) SetupWithManager(mgr manager.Manager) error {
76+
if r.PodExec == nil {
77+
var err error
78+
r.PodExec, err = runtime.NewPodExecutor(mgr.GetConfig())
79+
if err != nil {
80+
return err
81+
}
82+
}
6883
if err := r.CrunchyController.Watch(source.Kind(mgr.GetCache(), &corev1.Secret{}, r.watchSecrets())); err != nil {
6984
return errors.Wrap(err, "unable to watch secrets")
7085
}
@@ -241,7 +256,9 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
241256
return reconcile.Result{}, errors.Wrap(err, "failed to handle monitor user password change")
242257
}
243258

244-
r.reconcileCustomExtensions(cr)
259+
if err := r.reconcileCustomExtensions(ctx, cr); err != nil {
260+
return reconcile.Result{}, errors.Wrap(err, "reconcile custom extensions")
261+
}
245262

246263
if err := r.reconcileScheduledBackups(ctx, cr); err != nil {
247264
return reconcile.Result{}, errors.Wrap(err, "reconcile scheduled backups")
@@ -531,15 +548,53 @@ func (r *PGClusterReconciler) handleMonitorUserPassChange(ctx context.Context, c
531548
return nil
532549
}
533550

534-
func (r *PGClusterReconciler) reconcileCustomExtensions(cr *v2.PerconaPGCluster) {
551+
func (r *PGClusterReconciler) reconcileCustomExtensions(ctx context.Context, cr *v2.PerconaPGCluster) error {
535552
if cr.Spec.Extensions.Storage.Secret == nil {
536-
return
553+
return nil
537554
}
538555

539556
extensionKeys := make([]string, 0)
557+
extensionNames := make([]string, 0)
558+
540559
for _, extension := range cr.Spec.Extensions.Custom {
541560
key := extensions.GetExtensionKey(cr.Spec.PostgresVersion, extension.Name, extension.Version)
542561
extensionKeys = append(extensionKeys, key)
562+
extensionNames = append(extensionNames, extension.Name)
563+
}
564+
565+
if cr.CompareVersion("2.6.0") >= 0 {
566+
var removedExtension []string
567+
installedExtensions := cr.Status.InstalledCustomExtensions
568+
crExtensions := make(map[string]struct{})
569+
for _, ext := range extensionNames {
570+
crExtensions[ext] = struct{}{}
571+
}
572+
573+
// Check for missing entries in crExtensions
574+
for _, ext := range installedExtensions {
575+
// If an object exists in installedExtensions but not in crExtensions, the extension should be deleted.
576+
if _, ok := crExtensions[ext]; !ok {
577+
removedExtension = append(removedExtension, ext)
578+
}
579+
}
580+
581+
if len(removedExtension) > 0 {
582+
disable := func(ctx context.Context, exec postgres.Executor) error {
583+
return errors.WithStack(disableCustomExtensionsInDB(ctx, exec, removedExtension))
584+
}
585+
586+
primary, err := perconaPG.GetPrimaryPod(ctx, r.Client, cr)
587+
if err != nil {
588+
return errors.New("primary pod not found")
589+
}
590+
591+
err = disable(ctx, func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, command ...string) error {
592+
return r.PodExec(ctx, primary.Namespace, primary.Name, naming.ContainerDatabase, stdin, stdout, stderr, command...)
593+
})
594+
if err != nil {
595+
return errors.Wrap(err, "deletion extension from installed")
596+
}
597+
}
543598
}
544599

545600
for i := 0; i < len(cr.Spec.InstanceSets); i++ {
@@ -556,6 +611,31 @@ func (r *PGClusterReconciler) reconcileCustomExtensions(cr *v2.PerconaPGCluster)
556611
))
557612
set.VolumeMounts = append(set.VolumeMounts, extensions.ExtensionVolumeMounts(cr.Spec.PostgresVersion)...)
558613
}
614+
return nil
615+
}
616+
617+
func disableCustomExtensionsInDB(ctx context.Context, exec postgres.Executor, customExtensionsForDeletion []string) error {
618+
log := logging.FromContext(ctx)
619+
620+
for _, extensionName := range customExtensionsForDeletion {
621+
sqlCommand := fmt.Sprintf(
622+
`SET client_min_messages = WARNING; DROP EXTENSION IF EXISTS %s;`,
623+
extensionName,
624+
)
625+
_, _, err := exec.ExecInAllDatabases(ctx,
626+
sqlCommand,
627+
map[string]string{
628+
"ON_ERROR_STOP": "on", // Abort when any one command fails.
629+
"QUIET": "on", // Do not print successful commands to stdout.
630+
},
631+
)
632+
633+
log.V(1).Info("extension was disabled ", "extensionName", extensionName)
634+
635+
return errors.Wrap(err, "custom extension deletion")
636+
}
637+
638+
return nil
559639
}
560640

561641
func isBackupRunning(ctx context.Context, cl client.Reader, cr *v2.PerconaPGCluster) (bool, error) {

Diff for: percona/controller/pgcluster/status.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package pgcluster
22

33
import (
44
"context"
5-
65
"github.com/pkg/errors"
76
corev1 "k8s.io/api/core/v1"
87
"k8s.io/apimachinery/pkg/types"
@@ -78,6 +77,11 @@ func (r *PGClusterReconciler) updateStatus(ctx context.Context, cr *v2.PerconaPG
7877
return errors.Wrap(err, "get app host")
7978
}
8079

80+
installedCustomExtensions := make([]string, 0)
81+
for _, extension := range cr.Spec.Extensions.Custom {
82+
installedCustomExtensions = append(installedCustomExtensions, extension.Name)
83+
}
84+
8185
var size, ready int32
8286
ss := make([]v2.PostgresInstanceSetStatus, 0, len(status.InstanceSets))
8387
for _, is := range status.InstanceSets {
@@ -110,7 +114,8 @@ func (r *PGClusterReconciler) updateStatus(ctx context.Context, cr *v2.PerconaPG
110114
Size: status.Proxy.PGBouncer.Replicas,
111115
Ready: status.Proxy.PGBouncer.ReadyReplicas,
112116
},
113-
Host: host,
117+
Host: host,
118+
InstalledCustomExtensions: installedCustomExtensions,
114119
}
115120

116121
cluster.Status.State = r.getState(cr, &cluster.Status, status)

Diff for: percona/postgres/common.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package perconaPG
2+
3+
import (
4+
"context"
5+
6+
"github.com/pkg/errors"
7+
corev1 "k8s.io/api/core/v1"
8+
"k8s.io/apimachinery/pkg/labels"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
10+
11+
v2 "github.com/percona/percona-postgresql-operator/pkg/apis/pgv2.percona.com/v2"
12+
)
13+
14+
func GetPrimaryPod(ctx context.Context, cli client.Client, cr *v2.PerconaPGCluster) (*corev1.Pod, error) {
15+
podList := &corev1.PodList{}
16+
err := cli.List(ctx, podList, &client.ListOptions{
17+
Namespace: cr.Namespace,
18+
LabelSelector: labels.SelectorFromSet(map[string]string{
19+
"app.kubernetes.io/instance": cr.Name,
20+
"postgres-operator.crunchydata.com/role": "master",
21+
}),
22+
})
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
if len(podList.Items) == 0 {
28+
return nil, errors.New("no primary pod found")
29+
}
30+
31+
if len(podList.Items) > 1 {
32+
return nil, errors.New("multiple primary pods found")
33+
}
34+
35+
return &podList.Items[0], nil
36+
}

Diff for: percona/watcher/wal.go

+3-28
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ import (
77
"time"
88

99
"github.com/pkg/errors"
10-
corev1 "k8s.io/api/core/v1"
1110
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1211
"k8s.io/apimachinery/pkg/fields"
13-
"k8s.io/apimachinery/pkg/labels"
1412
"sigs.k8s.io/controller-runtime/pkg/client"
1513
"sigs.k8s.io/controller-runtime/pkg/event"
1614

1715
"github.com/percona/percona-postgresql-operator/internal/logging"
1816
"github.com/percona/percona-postgresql-operator/percona/clientcmd"
1917
"github.com/percona/percona-postgresql-operator/percona/pgbackrest"
18+
perconaPG "github.com/percona/percona-postgresql-operator/percona/postgres"
2019
pgv2 "github.com/percona/percona-postgresql-operator/pkg/apis/pgv2.percona.com/v2"
2120
)
2221

@@ -153,7 +152,7 @@ func getLatestBackup(ctx context.Context, cli client.Client, cr *pgv2.PerconaPGC
153152
func GetLatestCommitTimestamp(ctx context.Context, cli client.Client, execCli *clientcmd.Client, cr *pgv2.PerconaPGCluster, backup *pgv2.PerconaPGBackup) (*metav1.Time, error) {
154153
log := logging.FromContext(ctx)
155154

156-
primary, err := getPrimaryPod(ctx, cli, cr)
155+
primary, err := perconaPG.GetPrimaryPod(ctx, cli, cr)
157156
if err != nil {
158157
return nil, PrimaryPodNotFound
159158
}
@@ -188,32 +187,8 @@ func GetLatestCommitTimestamp(ctx context.Context, cli client.Client, execCli *c
188187
return &commitTsMeta, nil
189188
}
190189

191-
func getPrimaryPod(ctx context.Context, cli client.Client, cr *pgv2.PerconaPGCluster) (*corev1.Pod, error) {
192-
podList := &corev1.PodList{}
193-
err := cli.List(ctx, podList, &client.ListOptions{
194-
Namespace: cr.Namespace,
195-
LabelSelector: labels.SelectorFromSet(map[string]string{
196-
"app.kubernetes.io/instance": cr.Name,
197-
"postgres-operator.crunchydata.com/role": "master",
198-
}),
199-
})
200-
if err != nil {
201-
return nil, err
202-
}
203-
204-
if len(podList.Items) == 0 {
205-
return nil, errors.New("no primary pod found")
206-
}
207-
208-
if len(podList.Items) > 1 {
209-
return nil, errors.New("multiple primary pods found")
210-
}
211-
212-
return &podList.Items[0], nil
213-
}
214-
215190
func getBackupStartTimestamp(ctx context.Context, cli client.Client, cr *pgv2.PerconaPGCluster, backup *pgv2.PerconaPGBackup) (time.Time, error) {
216-
primary, err := getPrimaryPod(ctx, cli, cr)
191+
primary, err := perconaPG.GetPrimaryPod(ctx, cli, cr)
217192
if err != nil {
218193
return time.Time{}, PrimaryPodNotFound
219194
}

Diff for: pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go

+4
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ type PerconaPGClusterStatus struct {
417417
// +optional
418418
// +operator-sdk:csv:customresourcedefinitions:type=status
419419
Host string `json:"host"`
420+
421+
// +optional
422+
// +operator-sdk:csv:customresourcedefinitions:type=status
423+
InstalledCustomExtensions []string `json:"installedCustomExtensions"`
420424
}
421425

422426
type Backups struct {

Diff for: pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)