Skip to content

Commit 47f283c

Browse files
committed
Updates to support changes starting in pgAdmin 9.3
Changes in the flags used by pgAdmin's setup.py for user managment start in pgAdmin 9.3. Issue: PGO-2686
1 parent 2037234 commit 47f283c

File tree

4 files changed

+112
-44
lines changed

4 files changed

+112
-44
lines changed

config/crd/bases/postgres-operator.crunchydata.com_pgadmins.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,6 +1900,10 @@ spec:
19001900
description: MajorVersion represents the major version of the running
19011901
pgAdmin.
19021902
type: integer
1903+
minorVersion:
1904+
description: MinorVersion represents the minor version of the running
1905+
pgAdmin.
1906+
type: string
19031907
observedGeneration:
19041908
description: observedGeneration represents the .metadata.generation
19051909
on which the status was based.

internal/controller/standalone_pgadmin/users.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,40 +79,55 @@ func (r *PGAdminReconciler) reconcilePGAdminUsers(ctx context.Context, pgadmin *
7979
return nil
8080
}
8181

82-
// If the pgAdmin version is not in the status or the image SHA has changed, get
83-
// the pgAdmin version and store it in the status.
84-
var pgadminVersion int
85-
if pgadmin.Status.MajorVersion == 0 || pgadmin.Status.ImageSHA != pgAdminImageSha {
86-
pgadminVersion, err = r.reconcilePGAdminMajorVersion(ctx, podExecutor)
82+
// If the pgAdmin major or minor version is not in the status or the image
83+
// SHA has changed, get the pgAdmin version and store it in the status.
84+
var pgadminMajorVersion int
85+
if pgadmin.Status.MajorVersion == 0 || pgadmin.Status.MinorVersion == "" ||
86+
pgadmin.Status.ImageSHA != pgAdminImageSha {
87+
88+
pgadminMinorVersion, err := r.reconcilePGAdminVersion(ctx, podExecutor)
8789
if err != nil {
8890
return err
8991
}
90-
pgadmin.Status.MajorVersion = pgadminVersion
92+
93+
// ensure minor version is valid before storing in status
94+
parsedMinorVersion, err := strconv.ParseFloat(pgadminMinorVersion, 64)
95+
if err != nil {
96+
return err
97+
}
98+
99+
// Note: "When converting a floating-point number to an integer, the
100+
// fraction is discarded (truncation towards zero)."
101+
// - https://go.dev/ref/spec#Conversions
102+
pgadminMajorVersion = int(parsedMinorVersion)
103+
104+
pgadmin.Status.MinorVersion = pgadminMinorVersion
105+
pgadmin.Status.MajorVersion = pgadminMajorVersion
91106
pgadmin.Status.ImageSHA = pgAdminImageSha
92107
} else {
93-
pgadminVersion = pgadmin.Status.MajorVersion
108+
pgadminMajorVersion = pgadmin.Status.MajorVersion
94109
}
95110

96111
// If the pgAdmin version is not v8 or higher, return early as user management is
97112
// only supported for pgAdmin v8 and higher.
98-
if pgadminVersion < 8 {
113+
if pgadminMajorVersion < 8 {
99114
// If pgAdmin version is less than v8 and user management is being attempted,
100115
// log a message clarifying that it is only supported for pgAdmin v8 and higher.
101116
if len(pgadmin.Spec.Users) > 0 {
102117
log.Info("User management is only supported for pgAdmin v8 and higher.",
103-
"pgadminVersion", pgadminVersion)
118+
"pgadminVersion", pgadminMajorVersion)
104119
}
105120
return err
106121
}
107122

108123
return r.writePGAdminUsers(ctx, pgadmin, podExecutor)
109124
}
110125

111-
// reconcilePGAdminMajorVersion execs into the pgAdmin pod and retrieves the pgAdmin major version
112-
func (r *PGAdminReconciler) reconcilePGAdminMajorVersion(ctx context.Context, exec Executor) (int, error) {
126+
// reconcilePGAdminVersion execs into the pgAdmin pod and retrieves the pgAdmin minor version
127+
func (r *PGAdminReconciler) reconcilePGAdminVersion(ctx context.Context, exec Executor) (string, error) {
113128
script := fmt.Sprintf(`
114129
PGADMIN_DIR=%s
115-
cd $PGADMIN_DIR && python3 -c "import config; print(config.APP_RELEASE)"
130+
cd $PGADMIN_DIR && python3 -c "import config; print(config.APP_VERSION)"
116131
`, pgAdminDir)
117132

118133
var stdin, stdout, stderr bytes.Buffer
@@ -121,10 +136,10 @@ cd $PGADMIN_DIR && python3 -c "import config; print(config.APP_RELEASE)"
121136
[]string{"bash", "-ceu", "--", script}...)
122137

123138
if err != nil {
124-
return 0, err
139+
return "", err
125140
}
126141

127-
return strconv.Atoi(strings.TrimSpace(stdout.String()))
142+
return strings.TrimSpace(stdout.String()), nil
128143
}
129144

130145
// writePGAdminUsers takes the users in the pgAdmin spec and writes (adds or updates) their data
@@ -170,10 +185,23 @@ cd $PGADMIN_DIR
170185
for _, user := range existingUsersArr {
171186
existingUsersMap[user.Username] = user
172187
}
188+
189+
var olderThan9_3 bool
190+
versionFloat, err := strconv.ParseFloat(pgadmin.Status.MinorVersion, 32)
191+
if err != nil {
192+
return err
193+
}
194+
if versionFloat < 9.3 {
195+
olderThan9_3 = true
196+
}
197+
173198
intentUsers := []pgAdminUserForJson{}
174199
for _, user := range pgadmin.Spec.Users {
175200
var stdin, stdout, stderr bytes.Buffer
176-
typeFlag := "--nonadmin"
201+
typeFlag := "--role User"
202+
if olderThan9_3 {
203+
typeFlag = "--nonadmin"
204+
}
177205
isAdmin := false
178206
if user.Role == "Administrator" {
179207
typeFlag = "--admin"

internal/controller/standalone_pgadmin/users_test.go

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,16 @@ func TestReconcilePGAdminUsers(t *testing.T) {
110110
assert.Equal(t, namespace, pgadmin.Namespace)
111111
assert.Equal(t, container, naming.ContainerPGAdmin)
112112

113-
// Simulate a v7 version of pgAdmin by setting stdout to "7" for
114-
// podexec call in reconcilePGAdminMajorVersion
115-
stdout.Write([]byte("7"))
113+
// Simulate a v7.1 version of pgAdmin by setting stdout to "7.1"
114+
// for podexec call in reconcilePGAdminVersion
115+
_, _ = stdout.Write([]byte("7.1"))
116116
return nil
117117
}
118118

119119
assert.NilError(t, r.reconcilePGAdminUsers(ctx, pgadmin))
120120
assert.Equal(t, calls, 1, "PodExec should be called once")
121121
assert.Equal(t, pgadmin.Status.MajorVersion, 7)
122+
assert.Equal(t, pgadmin.Status.MinorVersion, "7.1")
122123
assert.Equal(t, pgadmin.Status.ImageSHA, "fakeSHA")
123124
})
124125

@@ -145,20 +146,58 @@ func TestReconcilePGAdminUsers(t *testing.T) {
145146
) error {
146147
calls++
147148

148-
// Simulate a v7 version of pgAdmin by setting stdout to "7" for
149-
// podexec call in reconcilePGAdminMajorVersion
150-
stdout.Write([]byte("7"))
149+
// Simulate a v7.1 version of pgAdmin by setting stdout to "7.1"
150+
// for podexec call in reconcilePGAdminVersion
151+
_, _ = stdout.Write([]byte("7.1"))
151152
return nil
152153
}
153154

154155
assert.NilError(t, r.reconcilePGAdminUsers(ctx, pgadmin))
155156
assert.Equal(t, calls, 1, "PodExec should be called once")
156157
assert.Equal(t, pgadmin.Status.MajorVersion, 7)
158+
assert.Equal(t, pgadmin.Status.MinorVersion, "7.1")
157159
assert.Equal(t, pgadmin.Status.ImageSHA, "newFakeSHA")
158160
})
161+
162+
t.Run("PodHealthyBadVersion", func(t *testing.T) {
163+
pgadmin := pgadmin.DeepCopy()
164+
pod := pod.DeepCopy()
165+
166+
pod.DeletionTimestamp = nil
167+
pod.Status.ContainerStatuses =
168+
[]corev1.ContainerStatus{{Name: naming.ContainerPGAdmin}}
169+
pod.Status.ContainerStatuses[0].State.Running =
170+
new(corev1.ContainerStateRunning)
171+
pod.Status.ContainerStatuses[0].ImageID = "fakeSHA"
172+
173+
r := new(PGAdminReconciler)
174+
r.Client = fake.NewClientBuilder().WithObjects(pod).Build()
175+
176+
calls := 0
177+
r.PodExec = func(
178+
ctx context.Context, namespace, pod, container string,
179+
stdin io.Reader, stdout, stderr io.Writer, command ...string,
180+
) error {
181+
calls++
182+
183+
assert.Equal(t, pod, "pgadmin-123-0")
184+
assert.Equal(t, namespace, pgadmin.Namespace)
185+
assert.Equal(t, container, naming.ContainerPGAdmin)
186+
187+
// set expected version to something completely wrong
188+
_, _ = stdout.Write([]byte("woot"))
189+
return nil
190+
}
191+
192+
assert.ErrorContains(t, r.reconcilePGAdminUsers(ctx, pgadmin), "strconv.ParseFloat: parsing \"woot\": invalid syntax")
193+
assert.Equal(t, calls, 1, "PodExec should be called once")
194+
assert.Equal(t, pgadmin.Status.MajorVersion, 0)
195+
assert.Equal(t, pgadmin.Status.MinorVersion, "")
196+
assert.Equal(t, pgadmin.Status.ImageSHA, "")
197+
})
159198
}
160199

161-
func TestReconcilePGAdminMajorVersion(t *testing.T) {
200+
func TestReconcilePGAdminVersion(t *testing.T) {
162201
ctx := context.Background()
163202
pod := corev1.Pod{}
164203
pod.Namespace = "test-namespace"
@@ -180,30 +219,15 @@ func TestReconcilePGAdminMajorVersion(t *testing.T) {
180219
assert.Equal(t, namespace, "test-namespace")
181220
assert.Equal(t, container, naming.ContainerPGAdmin)
182221

183-
// Simulate a v7 version of pgAdmin by setting stdout to "7" for
184-
// podexec call in reconcilePGAdminMajorVersion
185-
stdout.Write([]byte("7"))
222+
// Simulate a v9.3 version of pgAdmin by setting stdout to "9.3"
223+
// for podexec call in reconcilePGAdminVersion
224+
_, _ = stdout.Write([]byte("9.3"))
186225
return nil
187226
}
188227

189-
version, err := reconciler.reconcilePGAdminMajorVersion(ctx, podExecutor)
228+
version, err := reconciler.reconcilePGAdminVersion(ctx, podExecutor)
190229
assert.NilError(t, err)
191-
assert.Equal(t, version, 7)
192-
})
193-
194-
t.Run("FailedRetrieval", func(t *testing.T) {
195-
reconciler.PodExec = func(
196-
ctx context.Context, namespace, pod, container string,
197-
stdin io.Reader, stdout, stderr io.Writer, command ...string,
198-
) error {
199-
// Simulate the python call giving bad data (not a version int)
200-
stdout.Write([]byte("asdfjkl;"))
201-
return nil
202-
}
203-
204-
version, err := reconciler.reconcilePGAdminMajorVersion(ctx, podExecutor)
205-
assert.Check(t, err != nil)
206-
assert.Equal(t, version, 0)
230+
assert.Equal(t, version, "9.3")
207231
})
208232

209233
t.Run("PodExecError", func(t *testing.T) {
@@ -214,9 +238,9 @@ func TestReconcilePGAdminMajorVersion(t *testing.T) {
214238
return errors.New("PodExecError")
215239
}
216240

217-
version, err := reconciler.reconcilePGAdminMajorVersion(ctx, podExecutor)
241+
version, err := reconciler.reconcilePGAdminVersion(ctx, podExecutor)
218242
assert.Check(t, err != nil)
219-
assert.Equal(t, version, 0)
243+
assert.Equal(t, version, "")
220244
})
221245
}
222246

@@ -238,6 +262,14 @@ func TestWritePGAdminUsers(t *testing.T) {
238262
pgadmin.Namespace = ns.Name
239263
assert.NilError(t, cc.Create(ctx, pgadmin))
240264

265+
// fake the status so that the correct commands will be used when creating
266+
// users.
267+
pgadmin.Status = v1beta1.PGAdminStatus{
268+
ImageSHA: "fakesha",
269+
MajorVersion: 9,
270+
MinorVersion: "9.3",
271+
}
272+
241273
userPasswordSecret1 := &corev1.Secret{
242274
ObjectMeta: metav1.ObjectMeta{
243275
Name: "user-password-secret1",

pkg/apis/postgres-operator.crunchydata.com/v1beta1/standalone_pgadmin_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ type PGAdminStatus struct {
175175
// +optional
176176
MajorVersion int `json:"majorVersion,omitempty"`
177177

178+
// MinorVersion represents the minor version of the running pgAdmin.
179+
// +optional
180+
MinorVersion string `json:"minorVersion,omitempty"`
181+
178182
// observedGeneration represents the .metadata.generation on which the status was based.
179183
// +optional
180184
// +kubebuilder:validation:Minimum=0

0 commit comments

Comments
 (0)