Skip to content

Commit 25289eb

Browse files
committed
Shrink the initialize package by using generics
1 parent fc0aee0 commit 25289eb

File tree

24 files changed

+121
-209
lines changed

24 files changed

+121
-209
lines changed

internal/bridge/crunchybridgecluster/postgres.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"sigs.k8s.io/controller-runtime/pkg/client"
1717

1818
"github.com/crunchydata/postgres-operator/internal/bridge"
19-
"github.com/crunchydata/postgres-operator/internal/initialize"
2019
"github.com/crunchydata/postgres-operator/internal/naming"
2120
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
2221
)
@@ -34,11 +33,11 @@ func (r *CrunchyBridgeClusterReconciler) generatePostgresRoleSecret(
3433
Name: secretName,
3534
}}
3635
intent.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret"))
37-
initialize.StringMap(&intent.StringData)
38-
39-
intent.StringData["name"] = clusterRole.Name
40-
intent.StringData["password"] = clusterRole.Password
41-
intent.StringData["uri"] = clusterRole.URI
36+
intent.StringData = map[string]string{
37+
"name": clusterRole.Name,
38+
"password": clusterRole.Password,
39+
"uri": clusterRole.URI,
40+
}
4241

4342
intent.Annotations = cluster.Spec.Metadata.GetAnnotationsOrNil()
4443
intent.Labels = naming.Merge(

internal/controller/pgupgrade/jobs.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ func (r *PGUpgradeReconciler) generateUpgradeJob(
182182

183183
// The following will set these fields to null if not set in the spec
184184
job.Spec.Template.Spec.Affinity = upgrade.Spec.Affinity
185-
job.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(
186-
upgrade.Spec.PriorityClassName)
185+
job.Spec.Template.Spec.PriorityClassName =
186+
initialize.FromPointer(upgrade.Spec.PriorityClassName)
187187
job.Spec.Template.Spec.Tolerations = upgrade.Spec.Tolerations
188188

189189
r.setControllerReference(upgrade, job)
@@ -292,8 +292,8 @@ func (r *PGUpgradeReconciler) generateRemoveDataJob(
292292

293293
// The following will set these fields to null if not set in the spec
294294
job.Spec.Template.Spec.Affinity = upgrade.Spec.Affinity
295-
job.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(
296-
upgrade.Spec.PriorityClassName)
295+
job.Spec.Template.Spec.PriorityClassName =
296+
initialize.FromPointer(upgrade.Spec.PriorityClassName)
297297
job.Spec.Template.Spec.Tolerations = upgrade.Spec.Tolerations
298298

299299
r.setControllerReference(upgrade, job)

internal/controller/postgrescluster/instance.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,15 +1298,11 @@ func generateInstanceStatefulSetIntent(_ context.Context,
12981298
sts.Spec.Template.Spec.Affinity = spec.Affinity
12991299
sts.Spec.Template.Spec.Tolerations = spec.Tolerations
13001300
sts.Spec.Template.Spec.TopologySpreadConstraints = spec.TopologySpreadConstraints
1301-
if spec.PriorityClassName != nil {
1302-
sts.Spec.Template.Spec.PriorityClassName = *spec.PriorityClassName
1303-
}
1301+
sts.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(spec.PriorityClassName)
13041302

13051303
// if default pod scheduling is not explicitly disabled, add the default
13061304
// pod topology spread constraints
1307-
if cluster.Spec.DisableDefaultPodScheduling == nil ||
1308-
(cluster.Spec.DisableDefaultPodScheduling != nil &&
1309-
!*cluster.Spec.DisableDefaultPodScheduling) {
1305+
if !initialize.FromPointer(cluster.Spec.DisableDefaultPodScheduling) {
13101306
sts.Spec.Template.Spec.TopologySpreadConstraints = append(
13111307
sts.Spec.Template.Spec.TopologySpreadConstraints,
13121308
defaultTopologySpreadConstraints(

internal/controller/postgrescluster/instance_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,7 +1972,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) {
19721972
cluster := testCluster()
19731973
cluster.Namespace = ns.Name
19741974
spec := &cluster.Spec.InstanceSets[0]
1975-
spec.MinAvailable = initialize.IntOrStringInt32(0)
1975+
spec.MinAvailable = initialize.Pointer(intstr.FromInt32(0))
19761976
assert.NilError(t, r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec))
19771977
assert.Assert(t, !foundPDB(cluster, spec))
19781978
})
@@ -1981,7 +1981,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) {
19811981
cluster := testCluster()
19821982
cluster.Namespace = ns.Name
19831983
spec := &cluster.Spec.InstanceSets[0]
1984-
spec.MinAvailable = initialize.IntOrStringInt32(1)
1984+
spec.MinAvailable = initialize.Pointer(intstr.FromInt32(1))
19851985

19861986
assert.NilError(t, r.Client.Create(ctx, cluster))
19871987
t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) })
@@ -1990,7 +1990,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) {
19901990
assert.Assert(t, foundPDB(cluster, spec))
19911991

19921992
t.Run("deleted", func(t *testing.T) {
1993-
spec.MinAvailable = initialize.IntOrStringInt32(0)
1993+
spec.MinAvailable = initialize.Pointer(intstr.FromInt32(0))
19941994
err := r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec)
19951995
if apierrors.IsConflict(err) {
19961996
// When running in an existing environment another controller will sometimes update
@@ -2008,7 +2008,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) {
20082008
cluster := testCluster()
20092009
cluster.Namespace = ns.Name
20102010
spec := &cluster.Spec.InstanceSets[0]
2011-
spec.MinAvailable = initialize.IntOrStringString("50%")
2011+
spec.MinAvailable = initialize.Pointer(intstr.FromString("50%"))
20122012

20132013
assert.NilError(t, r.Client.Create(ctx, cluster))
20142014
t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) })
@@ -2017,7 +2017,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) {
20172017
assert.Assert(t, foundPDB(cluster, spec))
20182018

20192019
t.Run("deleted", func(t *testing.T) {
2020-
spec.MinAvailable = initialize.IntOrStringString("0%")
2020+
spec.MinAvailable = initialize.Pointer(intstr.FromString("0%"))
20212021
err := r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec)
20222022
if apierrors.IsConflict(err) {
20232023
// When running in an existing environment another controller will sometimes update
@@ -2031,13 +2031,13 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) {
20312031
})
20322032

20332033
t.Run("delete with 00%", func(t *testing.T) {
2034-
spec.MinAvailable = initialize.IntOrStringString("50%")
2034+
spec.MinAvailable = initialize.Pointer(intstr.FromString("50%"))
20352035

20362036
assert.NilError(t, r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec))
20372037
assert.Assert(t, foundPDB(cluster, spec))
20382038

20392039
t.Run("deleted", func(t *testing.T) {
2040-
spec.MinAvailable = initialize.IntOrStringString("00%")
2040+
spec.MinAvailable = initialize.Pointer(intstr.FromString("00%"))
20412041
err := r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec)
20422042
if apierrors.IsConflict(err) {
20432043
// When running in an existing environment another controller will sometimes update
@@ -2110,13 +2110,13 @@ func TestCleanupDisruptionBudgets(t *testing.T) {
21102110
cluster := testCluster()
21112111
cluster.Namespace = ns.Name
21122112
spec := &cluster.Spec.InstanceSets[0]
2113-
spec.MinAvailable = initialize.IntOrStringInt32(1)
2113+
spec.MinAvailable = initialize.Pointer(intstr.FromInt32(1))
21142114

21152115
assert.NilError(t, r.Client.Create(ctx, cluster))
21162116
t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) })
21172117

21182118
expectedPDB := generatePDB(t, cluster, spec,
2119-
initialize.IntOrStringInt32(1))
2119+
initialize.Pointer(intstr.FromInt32(1)))
21202120
assert.NilError(t, createPDB(expectedPDB))
21212121

21222122
t.Run("no instances were removed", func(t *testing.T) {
@@ -2129,7 +2129,7 @@ func TestCleanupDisruptionBudgets(t *testing.T) {
21292129
leftoverPDB := generatePDB(t, cluster, &v1beta1.PostgresInstanceSetSpec{
21302130
Name: "old-instance",
21312131
Replicas: initialize.Int32(1),
2132-
}, initialize.IntOrStringInt32(1))
2132+
}, initialize.Pointer(intstr.FromInt32(1)))
21332133
assert.NilError(t, createPDB(leftoverPDB))
21342134

21352135
assert.Assert(t, foundPDB(expectedPDB))

internal/controller/postgrescluster/pgadmin.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func (r *Reconciler) generatePGAdminService(
158158
// requires updates to the pgAdmin service configuration.
159159
servicePort := corev1.ServicePort{
160160
Name: naming.PortPGAdmin,
161-
Port: *initialize.Int32(5050),
161+
Port: 5050,
162162
Protocol: corev1.ProtocolTCP,
163163
TargetPort: intstr.FromString(naming.PortPGAdmin),
164164
}
@@ -294,11 +294,8 @@ func (r *Reconciler) reconcilePGAdminStatefulSet(
294294
// Use scheduling constraints from the cluster spec.
295295
sts.Spec.Template.Spec.Affinity = cluster.Spec.UserInterface.PGAdmin.Affinity
296296
sts.Spec.Template.Spec.Tolerations = cluster.Spec.UserInterface.PGAdmin.Tolerations
297-
298-
if cluster.Spec.UserInterface.PGAdmin.PriorityClassName != nil {
299-
sts.Spec.Template.Spec.PriorityClassName = *cluster.Spec.UserInterface.PGAdmin.PriorityClassName
300-
}
301-
297+
sts.Spec.Template.Spec.PriorityClassName =
298+
initialize.FromPointer(cluster.Spec.UserInterface.PGAdmin.PriorityClassName)
302299
sts.Spec.Template.Spec.TopologySpreadConstraints =
303300
cluster.Spec.UserInterface.PGAdmin.TopologySpreadConstraints
304301

internal/controller/postgrescluster/pgbackrest.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -620,16 +620,12 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster
620620
repo.Spec.Template.Spec.Affinity = repoHost.Affinity
621621
repo.Spec.Template.Spec.Tolerations = repoHost.Tolerations
622622
repo.Spec.Template.Spec.TopologySpreadConstraints = repoHost.TopologySpreadConstraints
623-
if repoHost.PriorityClassName != nil {
624-
repo.Spec.Template.Spec.PriorityClassName = *repoHost.PriorityClassName
625-
}
623+
repo.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(repoHost.PriorityClassName)
626624
}
627625

628626
// if default pod scheduling is not explicitly disabled, add the default
629627
// pod topology spread constraints
630-
if postgresCluster.Spec.DisableDefaultPodScheduling == nil ||
631-
(postgresCluster.Spec.DisableDefaultPodScheduling != nil &&
632-
!*postgresCluster.Spec.DisableDefaultPodScheduling) {
628+
if !initialize.FromPointer(postgresCluster.Spec.DisableDefaultPodScheduling) {
633629
repo.Spec.Template.Spec.TopologySpreadConstraints = append(
634630
repo.Spec.Template.Spec.TopologySpreadConstraints,
635631
defaultTopologySpreadConstraints(
@@ -836,12 +832,10 @@ func generateBackupJobSpecIntent(ctx context.Context, postgresCluster *v1beta1.P
836832

837833
// set the priority class name, tolerations, and affinity, if they exist
838834
if postgresCluster.Spec.Backups.PGBackRest.Jobs != nil {
839-
if postgresCluster.Spec.Backups.PGBackRest.Jobs.PriorityClassName != nil {
840-
jobSpec.Template.Spec.PriorityClassName =
841-
*postgresCluster.Spec.Backups.PGBackRest.Jobs.PriorityClassName
842-
}
843835
jobSpec.Template.Spec.Tolerations = postgresCluster.Spec.Backups.PGBackRest.Jobs.Tolerations
844836
jobSpec.Template.Spec.Affinity = postgresCluster.Spec.Backups.PGBackRest.Jobs.Affinity
837+
jobSpec.Template.Spec.PriorityClassName =
838+
initialize.FromPointer(postgresCluster.Spec.Backups.PGBackRest.Jobs.PriorityClassName)
845839
}
846840

847841
// Set the image pull secrets, if any exist.
@@ -1333,9 +1327,7 @@ func (r *Reconciler) generateRestoreJobIntent(cluster *v1beta1.PostgresCluster,
13331327
job.Spec.Template.Spec.SecurityContext = postgres.PodSecurityContext(cluster)
13341328

13351329
// set the priority class name, if it exists
1336-
if dataSource.PriorityClassName != nil {
1337-
job.Spec.Template.Spec.PriorityClassName = *dataSource.PriorityClassName
1338-
}
1330+
job.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(dataSource.PriorityClassName)
13391331

13401332
job.SetGroupVersionKind(batchv1.SchemeGroupVersion.WithKind("Job"))
13411333
if err := errors.WithStack(r.setControllerReference(cluster, job)); err != nil {

internal/controller/postgrescluster/pgbouncer.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -395,25 +395,20 @@ func (r *Reconciler) generatePGBouncerDeployment(
395395
// - https://docs.k8s.io/concepts/workloads/controllers/deployment/#rolling-update-deployment
396396
deploy.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
397397
deploy.Spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
398-
MaxUnavailable: intstr.ValueOrDefault(nil, intstr.FromInt(0)),
398+
MaxUnavailable: initialize.Pointer(intstr.FromInt32(0)),
399399
}
400400

401401
// Use scheduling constraints from the cluster spec.
402402
deploy.Spec.Template.Spec.Affinity = cluster.Spec.Proxy.PGBouncer.Affinity
403403
deploy.Spec.Template.Spec.Tolerations = cluster.Spec.Proxy.PGBouncer.Tolerations
404-
405-
if cluster.Spec.Proxy.PGBouncer.PriorityClassName != nil {
406-
deploy.Spec.Template.Spec.PriorityClassName = *cluster.Spec.Proxy.PGBouncer.PriorityClassName
407-
}
408-
404+
deploy.Spec.Template.Spec.PriorityClassName =
405+
initialize.FromPointer(cluster.Spec.Proxy.PGBouncer.PriorityClassName)
409406
deploy.Spec.Template.Spec.TopologySpreadConstraints =
410407
cluster.Spec.Proxy.PGBouncer.TopologySpreadConstraints
411408

412409
// if default pod scheduling is not explicitly disabled, add the default
413410
// pod topology spread constraints
414-
if cluster.Spec.DisableDefaultPodScheduling == nil ||
415-
(cluster.Spec.DisableDefaultPodScheduling != nil &&
416-
!*cluster.Spec.DisableDefaultPodScheduling) {
411+
if !initialize.FromPointer(cluster.Spec.DisableDefaultPodScheduling) {
417412
deploy.Spec.Template.Spec.TopologySpreadConstraints = append(
418413
deploy.Spec.Template.Spec.TopologySpreadConstraints,
419414
defaultTopologySpreadConstraints(*deploy.Spec.Selector)...)

internal/controller/postgrescluster/pgbouncer_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
policyv1 "k8s.io/api/policy/v1"
1616
apierrors "k8s.io/apimachinery/pkg/api/errors"
1717
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"k8s.io/apimachinery/pkg/util/intstr"
1819
"k8s.io/client-go/tools/record"
1920
"sigs.k8s.io/controller-runtime/pkg/client"
2021

@@ -551,7 +552,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) {
551552
cluster := testCluster()
552553
cluster.Namespace = ns.Name
553554
cluster.Spec.Proxy.PGBouncer.Replicas = initialize.Int32(1)
554-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringInt32(0)
555+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromInt32(0))
555556
assert.NilError(t, r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster))
556557
assert.Assert(t, !foundPDB(cluster))
557558
})
@@ -560,7 +561,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) {
560561
cluster := testCluster()
561562
cluster.Namespace = ns.Name
562563
cluster.Spec.Proxy.PGBouncer.Replicas = initialize.Int32(1)
563-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringInt32(1)
564+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromInt32(1))
564565

565566
assert.NilError(t, r.Client.Create(ctx, cluster))
566567
t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) })
@@ -569,7 +570,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) {
569570
assert.Assert(t, foundPDB(cluster))
570571

571572
t.Run("deleted", func(t *testing.T) {
572-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringInt32(0)
573+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromInt32(0))
573574
err := r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster)
574575
if apierrors.IsConflict(err) {
575576
// When running in an existing environment another controller will sometimes update
@@ -587,7 +588,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) {
587588
cluster := testCluster()
588589
cluster.Namespace = ns.Name
589590
cluster.Spec.Proxy.PGBouncer.Replicas = initialize.Int32(1)
590-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("50%")
591+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("50%"))
591592

592593
assert.NilError(t, r.Client.Create(ctx, cluster))
593594
t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) })
@@ -596,7 +597,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) {
596597
assert.Assert(t, foundPDB(cluster))
597598

598599
t.Run("deleted", func(t *testing.T) {
599-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("0%")
600+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("0%"))
600601
err := r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster)
601602
if apierrors.IsConflict(err) {
602603
// When running in an existing environment another controller will sometimes update
@@ -610,13 +611,13 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) {
610611
})
611612

612613
t.Run("delete with 00%", func(t *testing.T) {
613-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("50%")
614+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("50%"))
614615

615616
assert.NilError(t, r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster))
616617
assert.Assert(t, foundPDB(cluster))
617618

618619
t.Run("deleted", func(t *testing.T) {
619-
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("00%")
620+
cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("00%"))
620621
err := r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster)
621622
if apierrors.IsConflict(err) {
622623
// When running in an existing environment another controller will sometimes update

internal/controller/postgrescluster/pod_disruption_budget.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,5 @@ func getMinAvailable(
6464
}
6565

6666
// If more than one replica is not defined, we will default to '0'
67-
return initialize.IntOrStringInt32(expect)
67+
return initialize.Pointer(intstr.FromInt32(expect))
6868
}

internal/controller/postgrescluster/pod_disruption_budget_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestGeneratePodDisruptionBudget(t *testing.T) {
5050
"anno-key": "anno-value",
5151
},
5252
}
53-
minAvailable = initialize.IntOrStringInt32(1)
53+
minAvailable = initialize.Pointer(intstr.FromInt32(1))
5454
selector := metav1.LabelSelector{
5555
MatchLabels: map[string]string{
5656
"key": "value",
@@ -78,19 +78,19 @@ func TestGeneratePodDisruptionBudget(t *testing.T) {
7878
func TestGetMinAvailable(t *testing.T) {
7979
t.Run("minAvailable provided", func(t *testing.T) {
8080
// minAvailable is defined so use that value
81-
ma := initialize.IntOrStringInt32(0)
81+
ma := initialize.Pointer(intstr.FromInt32(0))
8282
expect := getMinAvailable(ma, 1)
8383
assert.Equal(t, *expect, intstr.FromInt(0))
8484

85-
ma = initialize.IntOrStringInt32(1)
85+
ma = initialize.Pointer(intstr.FromInt32(1))
8686
expect = getMinAvailable(ma, 2)
8787
assert.Equal(t, *expect, intstr.FromInt(1))
8888

89-
ma = initialize.IntOrStringString("50%")
89+
ma = initialize.Pointer(intstr.FromString("50%"))
9090
expect = getMinAvailable(ma, 3)
9191
assert.Equal(t, *expect, intstr.FromString("50%"))
9292

93-
ma = initialize.IntOrStringString("200%")
93+
ma = initialize.Pointer(intstr.FromString("200%"))
9494
expect = getMinAvailable(ma, 2147483647)
9595
assert.Equal(t, *expect, intstr.FromString("200%"))
9696
})

0 commit comments

Comments
 (0)