Skip to content

Commit

Permalink
Merge branch 'release/v1.10.x' into merge/v1.10.2
Browse files Browse the repository at this point in the history
* release/v1.10.x:
  DEVOPS-30046 DEVOPS-30239 dsnexec conversion injects dbproxy (#381)
  update start-pgbouncer to exit after 60seconds (#379)
  PTEUDO-2142 remove validation of DSNName (#370)
  update CI pipeline for gitflow branch names
  • Loading branch information
drewwells committed Dec 18, 2024
2 parents 071bee2 + 5d9094f commit 88529d5
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 90 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: Build and push :main image

on:
push:
branches: [ main ]
branches:
- main
- 'release/**'

env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Expand Down
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pipeline {
anyOf {
branch 'main'
branch 'hotfix/*'
branch 'release/*'
}
expression { !isPrBuild() }
}
Expand Down
71 changes: 55 additions & 16 deletions dbproxy/scripts/start-pgbouncer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,65 @@ set -e
command -v pgbouncer
set +e

until timeout 10 psql "$(cat /dbproxy/uri_dsn.txt)" -c 'SELECT 1'; do
echo "Waiting for PostgreSQL to be ready..."
sleep 1
done
echo "PostgreSQL is ready!"
# Function to test PostgreSQL connection
test_postgres() {
local dsn="$1"
if psql "$dsn" -c 'SELECT 1' >/dev/null 2>&1; then
return 0
fi
return 1
}

# Function to run all connection tests with global timeout
run_connection_tests() {
local start_time=$(date +%s)
local timeout=60
local ssl_ok=0
local nonssl_ok=0

while [ $ssl_ok -eq 0 ] || [ $nonssl_ok -eq 0 ]; do
current_time=$(date +%s)
if [ $((current_time - start_time)) -ge $timeout ]; then
echo "Timed out waiting for PostgreSQL after ${timeout} seconds"
return 1
fi

if [ $ssl_ok -eq 0 ] && test_postgres "postgres://localhost:5432/?sslmode=require"; then
echo "SSL connection successful"
ssl_ok=1
fi

if [ $nonssl_ok -eq 0 ] && test_postgres "postgres://localhost:5432/?sslmode=disable"; then
echo "Non-SSL connection successful"
nonssl_ok=1
fi

if [ $ssl_ok -eq 0 ] || [ $nonssl_ok -eq 0 ]; then
echo "Waiting for PostgreSQL connections to be ready..."
sleep 2
fi
done
return 0
}

# Initial PostgreSQL check
if ! test_postgres "$(cat /dbproxy/uri_dsn.txt)"; then
echo "Initial PostgreSQL connection failed"
exit 1
fi
echo "Initial PostgreSQL connection successful!"

# Generate certificates
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout dbproxy-server.key -out dbproxy-server.crt -subj "/C=US/CN=dbproxy-server/ST=CA/L=Santa Clara/O=Infoblox/OU=Blox in a Box"
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout dbproxy-client.key -out dbproxy-client.crt -subj "/C=US/CN=dbproxy-client/ST=CA/L=Santa Clara/O=Infoblox/OU=Blox in a Box"

# Start pgbouncer
pgbouncer -d -v pgbouncer.ini

# Test that both SSL and non-SSL connections work
until timeout 10 psql postgres://localhost:5432/?sslmode=require -c 'SELECT 1'; do
echo "Waiting for PostgreSQL to be ready..."
sleep 1
done

until timeout 10 psql postgres://localhost:5432/?sslmode=disable -c 'SELECT 1'; do
echo "Waiting for PostgreSQL to be ready..."
sleep 1
done
# Test both SSL and non-SSL connections concurrently
if ! run_connection_tests; then
echo "Connection tests failed"
exit 1
fi

echo "PostgreSQL is ready!"
echo "All PostgreSQL connections are ready!"
81 changes: 66 additions & 15 deletions internal/controller/databaseclaim_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

persistancev1 "github.com/infobloxopen/db-controller/api/v1"
v1 "github.com/infobloxopen/db-controller/api/v1"
"github.com/infobloxopen/db-controller/internal/dockerdb"
"github.com/infobloxopen/db-controller/pkg/hostparams"
)

Expand Down Expand Up @@ -67,6 +69,25 @@ var _ = Describe("DatabaseClaim Controller", func() {
password, ok := parsedDSN.User.Password()
Expect(ok).To(BeTrue())

secret := &corev1.Secret{}
err = k8sClient.Get(ctx, typeNamespacedSecretName, secret)
Expect(err).To(HaveOccurred())
Expect(client.IgnoreNotFound(err)).To(Succeed())

By(fmt.Sprintf("creating master credentials: %s", secretName))
secret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: "default",
},
StringData: map[string]string{
"password": password,
},
Type: "Opaque",
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())

By("creating master databaseclaims")
Expect(client.IgnoreNotFound(err)).To(Succeed())
resource := &persistancev1.DatabaseClaim{
TypeMeta: metav1.TypeMeta{
Expand All @@ -78,6 +99,8 @@ var _ = Describe("DatabaseClaim Controller", func() {
Namespace: "default",
},
Spec: persistancev1.DatabaseClaimSpec{
// TODO: remove customization of DSNName
DSNName: "fixme.txt",
Class: ptr.To(""),
DatabaseName: "sample_app",
SecretName: secretName,
Expand All @@ -90,22 +113,14 @@ var _ = Describe("DatabaseClaim Controller", func() {
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())

secret := &corev1.Secret{}
err = k8sClient.Get(ctx, typeNamespacedSecretName, secret)
Expect(err).To(HaveOccurred())
Expect(client.IgnoreNotFound(err)).To(Succeed())
By("Mocking master credentials")
hostParams, err := hostparams.New(controllerReconciler.Config.Viper, resource)
Expect(err).ToNot(HaveOccurred())

secret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: "default",
},
StringData: map[string]string{
"password": password,
},
Type: "Opaque",
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
credSecretName := fmt.Sprintf("%s-%s-%s", env, resourceName, hostParams.Hash())

cleanup := dockerdb.MockRDSCredentials(GinkgoT(), ctx, k8sClient, testDSN, credSecretName)
DeferCleanup(cleanup)

})

Expand Down Expand Up @@ -146,6 +161,42 @@ var _ = Describe("DatabaseClaim Controller", func() {
Expect(claim.Status.Error).To(Equal(""))
})

It("Should have DSN and URIDSN keys populated", func() {
By("Reconciling the created resource")
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
Expect(err).NotTo(HaveOccurred())

var claim persistancev1.DatabaseClaim
err = k8sClient.Get(ctx, typeNamespacedName, &claim)
Expect(err).NotTo(HaveOccurred())
Expect(claim.Status.Error).To(Equal(""))

By("Checking the user credentials secret")

secret := &corev1.Secret{}
err = k8sClient.Get(ctx, typeNamespacedSecretName, secret)
Expect(err).NotTo(HaveOccurred())

for _, key := range []string{v1.DSNKey, v1.DSNURIKey, "fixme.txt", "uri_fixme.txt"} {
Expect(secret.Data[key]).NotTo(BeNil())
}
oldKey := secret.Data[v1.DSNKey]
Expect(secret.Data[v1.DSNKey]).To(Equal(secret.Data["fixme.txt"]))
Expect(secret.Data[v1.DSNURIKey]).To(Equal(secret.Data["uri_fixme.txt"]))
// Slow down the test so creds are rotated, 60ns rotation time
By("Rotate passwords and verify credentials are updated")
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
Expect(err).NotTo(HaveOccurred())

err = k8sClient.Get(ctx, typeNamespacedSecretName, secret)
Expect(err).NotTo(HaveOccurred())

Expect(secret.Data[v1.DSNKey]).NotTo(Equal(oldKey))
Expect(secret.Data[v1.DSNKey]).To(Equal(secret.Data["fixme.txt"]))
Expect(secret.Data[v1.DSNURIKey]).To(Equal(secret.Data["uri_fixme.txt"]))

})

It("Should succeed with no error status to reconcile CR with DBVersion", func() {
By("Updating CR with a DB Version")

Expand Down
3 changes: 2 additions & 1 deletion internal/controller/databasecontroller_migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

persistancev1 "github.com/infobloxopen/db-controller/api/v1"
v1 "github.com/infobloxopen/db-controller/api/v1"
"github.com/infobloxopen/db-controller/internal/dockerdb"
"github.com/infobloxopen/db-controller/pkg/hostparams"
"github.com/infobloxopen/db-controller/pkg/pgctl"
Expand Down Expand Up @@ -152,7 +153,7 @@ var _ = Describe("claim migrate", func() {
Namespace: "default",
},
StringData: map[string]string{
"uri_dsn.txt": testDSN,
v1.DSNURIKey: testDSN,
},
Type: "Opaque",
}
Expand Down
15 changes: 11 additions & 4 deletions internal/dockerdb/mockdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"net/url"
"strings"

. "github.com/onsi/ginkgo/v2"

Expand All @@ -23,7 +22,16 @@ func MockRDS(t GinkgoTInterface, ctx context.Context, cli client.Client, secretN
DockerTag: "15",
})

fakeDSN = strings.Replace(fakeDSN, "localhost", "127.0.0.1", 1)
cleanSecret := MockRDSCredentials(t, ctx, cli, fakeDSN, secretName)

return dbCli, fakeDSN, func() {
cleanSecret()
clean()
}

}

func MockRDSCredentials(t GinkgoTInterface, ctx context.Context, cli client.Client, fakeDSN, secretName string) func() {

u, err := url.Parse(fakeDSN)
if err != nil {
Expand All @@ -47,10 +55,9 @@ func MockRDS(t GinkgoTInterface, ctx context.Context, cli client.Client, secretN
t.Fatalf("failed to create secret: %v", err)
}

return dbCli, fakeDSN, func() {
return func() {
if err := cli.Delete(ctx, secret); err != nil {
t.Logf("failed to delete secret: %v", err)
}
clean()
}
}
2 changes: 2 additions & 0 deletions internal/dockerdb/testdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ CREATE ROLE alloydbsuperuser WITH INHERIT LOGIN`)
logger.Info(string(out))
os.Exit(1)
}

dsn = strings.Replace(dsn, "localhost", "127.0.0.1", 1)
// TODO: change this to debug logging, just timing jenkins for now
logger.Info("db_connected", "dsn", dsn, "duration", time.Since(now))

Expand Down
59 changes: 37 additions & 22 deletions internal/webhook/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ import (
)

var (
// DSNExec annotations
DeprecatedAnnotationDSNExecConfig = "infoblox.com/dsnexec-config-secret"
DeprecatedAnnotationRemoteDBDSN = "infoblox.com/remote-db-dsn-secret"
DeprecatedAnnotationDBSecretPath = "infoblox.com/db-secret-path"
DeprecatedAnnotationMessages = "persistance.atlas.infoblox.com/deprecation-messages"
// DBProxy annotations
DeprecatedAnnotationDBSecretPath = "infoblox.com/db-secret-path"

DeprecatedAnnotationMessages = "persistance.atlas.infoblox.com/deprecation-messages"
)

// +kubebuilder:webhook:path=/convert-deprecated-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=podconversion.persistance.atlas.infoblox.com,sideEffects=None,timeoutSeconds=10,admissionReviewVersions=v1
Expand Down Expand Up @@ -94,10 +97,18 @@ func (p *podConverter) Handle(ctx context.Context, req admission.Request) admiss
}

// Check if any of the deprecated annotations are present
// dsnexec
dsnExecConfigSecret := pod.Annotations[DeprecatedAnnotationDSNExecConfig]
remoteDBDSNSecret := pod.Annotations[DeprecatedAnnotationRemoteDBDSN]
// dbproxy
dbSecretPath := pod.Annotations[DeprecatedAnnotationDBSecretPath]

if pod.Labels[LabelCheckExec] == "enabled" || pod.Labels[LabelCheckProxy] == "enabled" {
// This would log on every pod creation in the cluster
// log.V(1).Info("Skipped conversion, already converted", "uid", req.UID)
return admission.Allowed("Skipped conversion, already converted")
}

if dsnExecConfigSecret == "" && remoteDBDSNSecret == "" && dbSecretPath == "" {
// This would log on every pod creation in the cluster
// log.V(1).Info("Skipped conversion, no deprecated annotations found", "uid", req.UID)
Expand All @@ -112,7 +123,7 @@ func (p *podConverter) Handle(ctx context.Context, req admission.Request) admiss
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
log.Info("converted_pod")
log.Info("deprecated_pod_annotations_found")
return admission.PatchResponseFromRaw(req.Object.Raw, bs)
}

Expand All @@ -139,41 +150,45 @@ func convertPod(ctx context.Context, reader client.Reader, class string, pod *co
secretName = dbSecretPath
}

log = log.WithValues("secret", secretName)
log = log.WithValues("secret", secretName).WithValues("annotations", pod.Annotations).WithValues("labels", pod.Labels)

log.Info("converting_pod")

// db-secret-path has a key in it, so remove the key
parts := strings.Split(secretName, "/")
if len(parts) > 1 {
secretName = parts[0]
}

labelConfigExec := pod.Labels[LabelConfigExec]
if labelConfigExec == "" && dsnExecConfigSecret != "" {
pod.Labels[LabelConfigExec] = pod.Annotations[DeprecatedAnnotationDSNExecConfig]
pod.Labels[LabelCheckExec] = "enabled"
deprecationMsgs = append(deprecationMsgs, fmt.Sprintf(`Label "%s" replaces annotation "%s"`, LabelConfigExec, DeprecatedAnnotationDSNExecConfig))
var claimName string
var err error
if claimName, err = getClaimName(ctx, reader, pod.GetNamespace(), secretName); err != nil {
log.Error(err, "unable to find claim")
return err
}

// Process claims label
if pod.Labels[LabelClaim] == "" {
// dsnexec
if dsnExecConfigSecret != "" && remoteDBDSNSecret != "" {
pod.Labels[LabelClaim] = claimName
pod.Labels[LabelClass] = class
pod.Labels[LabelConfigExec] = dsnExecConfigSecret
pod.Labels[LabelCheckExec] = "enabled"

if pod.Annotations[DeprecatedAnnotationRemoteDBDSN] != "" {
deprecationMsgs = append(deprecationMsgs, fmt.Sprintf(`Label "%s" replaces annotation "%s"`, LabelClaim, DeprecatedAnnotationRemoteDBDSN))
}
if pod.Annotations[DeprecatedAnnotationDBSecretPath] != "" {
deprecationMsgs = append(deprecationMsgs, fmt.Sprintf(`Label "%s" replaces annotation "%s"`, LabelClaim, DeprecatedAnnotationDBSecretPath))
}
deprecationMsgs = append(deprecationMsgs, fmt.Sprintf(`Use label "%s", annotation "%s" is deprecated`, LabelConfigExec, DeprecatedAnnotationDSNExecConfig))

var claimName string
var err error
if claimName, err = getClaimName(ctx, reader, pod.GetNamespace(), secretName); err != nil {
log.Error(err, "unable to find claim")
return err
if pod.Annotations[DeprecatedAnnotationRemoteDBDSN] != "" {
deprecationMsgs = append(deprecationMsgs, fmt.Sprintf(`Use label "%s", annotation "%s" is deprecated`, LabelClaim, DeprecatedAnnotationRemoteDBDSN))
}
}

// dbproxy
if dbSecretPath != "" {
pod.Labels[LabelClaim] = claimName
pod.Labels[LabelClass] = class
pod.Labels[LabelCheckProxy] = "enabled"

deprecationMsgs = append(deprecationMsgs, fmt.Sprintf(`Label "%s" replaces annotation "%s"`, LabelClaim, DeprecatedAnnotationDBSecretPath))

}

// Remove deprecated annotations
Expand Down
Loading

0 comments on commit 88529d5

Please sign in to comment.