refactor: wire shared Instance dependencies at startup
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user