refactor: assemble and reuse Instance dependencies
E2E Tests / Run on Ubuntu (pull_request) Failing after 59s
Tests / Run on Ubuntu (pull_request) Successful in 3m55s
Lint / Run on Ubuntu (pull_request) Successful in 4m18s

This commit is contained in:
2026-09-11 15:54:05 +00:00
parent 89d9e2316b
commit a4d835e612
21 changed files with 975 additions and 340 deletions
+71
View File
@@ -0,0 +1,71 @@
/*
Copyright 2026.
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 main
import (
"errors"
"flag"
"strings"
"time"
"git.ddupan.top/panxiao81/postgresql-tenant-operator/internal/openbao"
bao "github.com/openbao/openbao/api/v2"
)
// dependencyOptions belongs to process startup, never to an Instance.
// Configuration is immutable after assembly; no configuration hot reload is enabled.
type dependencyOptions struct {
address string
consumerAddress string
authMount string
authRole string
kvMount string
tenantBasePath string
externalSecretStoreName string
postgreSQLCABundlePath string
timeout time.Duration
}
func (o *dependencyOptions) bindFlags(flags *flag.FlagSet) {
flags.StringVar(&o.address, "openbao-address", "", "OpenBao API address.")
flags.StringVar(&o.consumerAddress, "openbao-consumer-address", "", "Consumer OpenBao API address.")
flags.StringVar(&o.authMount, "openbao-auth-mount", "kubernetes", "OpenBao Kubernetes auth mount.")
flags.StringVar(&o.authRole, "openbao-auth-role", "", "OpenBao Kubernetes auth role.")
flags.StringVar(&o.kvMount, "openbao-kv-mount", "kv", "OpenBao KV v2 mount.")
flags.StringVar(&o.tenantBasePath, "openbao-tenant-base-path", "postgresql-tenants", "Tenant credential prefix.")
flags.StringVar(&o.externalSecretStoreName, "external-secret-store-name", "", "ESO ClusterSecretStore name.")
flags.StringVar(&o.postgreSQLCABundlePath, "postgresql-ca-bundle-path", "", "PostgreSQL CA bundle path.")
flags.DurationVar(&o.timeout, "reconcile-timeout", 30*time.Second, "External operation deadline.")
}
func (o dependencyOptions) credentials() (*openbao.Credentials, error) {
if o.address == "" || o.authRole == "" || o.authMount == "" || o.kvMount == "" {
return nil, errors.New("OpenBao address, auth role, auth mount and KV mount are required")
}
if o.timeout <= 0 {
return nil, errors.New("reconcile timeout must be positive")
}
// Delegate connection address parsing to the SDK, and avoid implicit BAO_* overrides.
config := bao.NewConfig()
config.Address = strings.TrimRight(o.address, "/")
config.Timeout = o.timeout
client, err := bao.NewClient(config)
if err != nil {
return nil, errors.New("invalid OpenBao client configuration")
}
return openbao.NewCredentials(client, o.kvMount, o.authMount, o.authRole), nil
}
+56
View File
@@ -0,0 +1,56 @@
/*
Copyright 2026.
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 main
import (
"flag"
"io"
"testing"
)
const testAddressFlag = "--openbao-address=http://bao:8200"
func TestDependencyConfigurationFailsBeforeAssembly(t *testing.T) {
for _, args := range [][]string{
{},
{testAddressFlag},
{testAddressFlag, "--openbao-auth-role=controller", "--reconcile-timeout=0s"},
} {
var options dependencyOptions
flags := flag.NewFlagSet("test", flag.ContinueOnError)
flags.SetOutput(io.Discard)
options.bindFlags(flags)
if err := flags.Parse(args); err != nil {
t.Fatal(err)
}
if _, err := options.credentials(); err == nil {
t.Fatal("invalid configuration was accepted")
}
}
}
func TestAssemblyDoesNotRequireLiveDependenciesOrTenantConfiguration(t *testing.T) {
var options dependencyOptions
flags := flag.NewFlagSet("test", flag.ContinueOnError)
options.bindFlags(flags)
if err := flags.Parse([]string{testAddressFlag, "--openbao-auth-role=controller"}); err != nil {
t.Fatal(err)
}
if _, err := options.credentials(); err != nil {
t.Fatal(err)
}
}
+15 -28
View File
@@ -20,7 +20,6 @@ import (
"crypto/tls"
"flag"
"os"
"time"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
@@ -38,7 +37,8 @@ import (
databasev1alpha1 "git.ddupan.top/panxiao81/postgresql-tenant-operator/api/v1alpha1"
"git.ddupan.top/panxiao81/postgresql-tenant-operator/internal/controller"
instanceinitializer "git.ddupan.top/panxiao81/postgresql-tenant-operator/internal/instance"
"git.ddupan.top/panxiao81/postgresql-tenant-operator/internal/instance"
"git.ddupan.top/panxiao81/postgresql-tenant-operator/internal/postgresql"
// +kubebuilder:scaffold:imports
)
@@ -63,10 +63,7 @@ func main() {
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var openBaoAddress, openBaoConsumerAddress, openBaoAuthMount, openBaoAuthRole string
var openBaoKVMount, openBaoTenantBasePath, openBaoServiceAccountTokenPath string
var externalSecretStoreName, postgreSQLCABundlePath string
var reconcileTimeout time.Duration
var dependencies dependencyOptions
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -85,20 +82,8 @@ func main() {
flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.StringVar(&openBaoAddress, "openbao-address", "", "OpenBao API address used by the controller.")
flag.StringVar(&openBaoConsumerAddress, "openbao-consumer-address", "", "OpenBao API address exposed to consumers.")
flag.StringVar(&openBaoAuthMount, "openbao-auth-mount", "kubernetes", "OpenBao Kubernetes auth mount.")
flag.StringVar(&openBaoAuthRole, "openbao-auth-role", "", "OpenBao Kubernetes auth role.")
flag.StringVar(&openBaoKVMount, "openbao-kv-mount", "kv", "OpenBao KV v2 mount.")
flag.StringVar(&openBaoServiceAccountTokenPath, "openbao-service-account-token-path",
"/var/run/secrets/kubernetes.io/serviceaccount/token",
"Projected service account token used for OpenBao authentication.")
flag.StringVar(&openBaoTenantBasePath, "openbao-tenant-base-path", "postgresql-tenants",
"Tenant credential base path.")
flag.StringVar(&externalSecretStoreName, "external-secret-store-name", "", "ESO ClusterSecretStore name.")
flag.StringVar(&postgreSQLCABundlePath, "postgresql-ca-bundle-path", "", "PostgreSQL CA bundle path.")
flag.DurationVar(&reconcileTimeout, "reconcile-timeout", 30*time.Second,
"Deadline for external operations in one reconcile.")
dependencies.bindFlags(flag.CommandLine)
opts := zap.Options{
Development: true,
}
@@ -107,17 +92,15 @@ func main() {
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
instanceInitializer, err := instanceinitializer.New(instanceinitializer.Config{
OpenBaoAddress: openBaoAddress, OpenBaoConsumerAddress: openBaoConsumerAddress,
OpenBaoAuthMount: openBaoAuthMount, OpenBaoAuthRole: openBaoAuthRole,
ServiceAccountTokenPath: openBaoServiceAccountTokenPath, OpenBaoKVMount: openBaoKVMount,
OpenBaoTenantBasePath: openBaoTenantBasePath, ExternalSecretStoreName: externalSecretStoreName,
PostgreSQLCABundlePath: postgreSQLCABundlePath, Timeout: reconcileTimeout,
})
credentials, err := dependencies.credentials()
if err != nil {
setupLog.Error(err, "Invalid controller dependency configuration")
os.Exit(1)
}
instances := instance.NewService(credentials, postgresql.Connector{
CABundlePath: dependencies.postgreSQLCABundlePath,
})
defer instances.Close()
// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
@@ -211,7 +194,10 @@ func main() {
}
if err := (&controller.PostgreSQLInstanceReconciler{
Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Initializer: instanceInitializer, Timeout: reconcileTimeout,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Instances: instances,
Timeout: dependencies.timeout,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "Failed to create controller", "controller", "postgresqlinstance")
os.Exit(1)
@@ -237,6 +223,7 @@ func main() {
setupLog.Info("Starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "Failed to run manager")
instances.Close()
os.Exit(1)
}
}