feat: 接入 Database 三资源 API 与分层绑定协调
确定单库单账号、集群级 Database、资源侧先写绑定和凭据定位合同。领域层承载纯规则,service 协調流程,Kubernetes adapter 负责资源呈现与版本保护。 验证:全量 make test、三轮 race、真实 API server 并发与重启补写、最小 RBAC/watch、lint 和文档检查通过。供应、凭据交付及删除清理尚未实现,保留 DeletionPending/finalizer 边界。
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package v1alpha1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
// ObjectName 定位集群级资源,不携带 namespace 或隐式跨 API group 引用。
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$`
|
||||
type ObjectName string
|
||||
|
||||
// PostgreSQLIdentifier 是第一版受管 database 与 login role 使用的名称。
|
||||
// +kubebuilder:validation:MaxLength=63
|
||||
// +kubebuilder:validation:Pattern=`^[a-z][a-z0-9_]{0,62}$`
|
||||
type PostgreSQLIdentifier string
|
||||
|
||||
// InstanceReference 仅引用同 API group 的集群级 PostgreSQLInstance。
|
||||
type InstanceReference struct {
|
||||
Name ObjectName `json:"name"`
|
||||
}
|
||||
|
||||
// DatabaseReference 是 Tenant 对已有集群级 PostgreSQLDatabase 的选择。
|
||||
type DatabaseReference struct {
|
||||
Name ObjectName `json:"name"`
|
||||
}
|
||||
|
||||
// BoundDatabaseReference 记录已经参与绑定的对象身份,而非仅记录可复用的名称。
|
||||
type BoundDatabaseReference struct {
|
||||
Name ObjectName `json:"name"`
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=128
|
||||
UID types.UID `json:"uid"`
|
||||
}
|
||||
|
||||
// TenantReference 是 Database 的当前绑定记录,不是允许绑定名单。
|
||||
type TenantReference struct {
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=63
|
||||
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
|
||||
Namespace string `json:"namespace"`
|
||||
Name ObjectName `json:"name"`
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=128
|
||||
UID types.UID `json:"uid"`
|
||||
}
|
||||
|
||||
// ReclaimPolicy 控制资源释放后的处置,只有资源管理者可以修改。
|
||||
// +kubebuilder:validation:Enum=Retain;Delete
|
||||
type ReclaimPolicy string
|
||||
|
||||
const (
|
||||
ReclaimRetain ReclaimPolicy = "Retain"
|
||||
ReclaimDelete ReclaimPolicy = "Delete"
|
||||
)
|
||||
|
||||
// CredentialReference 定位已有 OpenBao KV v2 凭据,不包含任何秘密值。
|
||||
// 只由资源管理员在导入时填写;controller 必须检查部署允许的 mount/path 范围。
|
||||
type CredentialReference struct {
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
Mount string `json:"mount"`
|
||||
// Path 是 mount 内的逻辑路径,不含 KV v2 的 data/ API 前缀。
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=1024
|
||||
Path string `json:"path"`
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Package v1alpha1 定义 Database 领域的 Kubernetes API。
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=database.ayatori.ddupan.top
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: "database.ayatori.ddupan.top", Version: "v1alpha1"}
|
||||
GroupVersion = SchemeGroupVersion
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&PostgreSQLInstance{}, &PostgreSQLInstanceList{},
|
||||
&PostgreSQLDatabase{}, &PostgreSQLDatabaseList{},
|
||||
&PostgreSQLTenant{}, &PostgreSQLTenantList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
})
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// PostgreSQLDatabaseSpec 是一库、一个 login owner 及凭据的独立资源声明。
|
||||
// +kubebuilder:validation:XValidation:rule="(self.source == 'Import') == has(self.credentialRef)",message="only imported databases require an existing credentialRef"
|
||||
type PostgreSQLDatabaseSpec struct {
|
||||
InstanceRef InstanceReference `json:"instanceRef"`
|
||||
Database PostgreSQLIdentifier `json:"database"`
|
||||
LoginRole PostgreSQLIdentifier `json:"loginRole"`
|
||||
// Source 明确区分创建与只读导入,不从后端同名对象推断。
|
||||
// +kubebuilder:validation:Enum=Provision;Import
|
||||
Source string `json:"source"`
|
||||
// +optional
|
||||
CredentialRef *CredentialReference `json:"credentialRef,omitempty"`
|
||||
// +kubebuilder:default=Retain
|
||||
// +optional
|
||||
ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"`
|
||||
// TenantRef 由 controller 先写入;Released 时仍保留旧身份。
|
||||
// +optional
|
||||
TenantRef *TenantReference `json:"tenantRef,omitempty"`
|
||||
}
|
||||
|
||||
type PostgreSQLDatabaseStatus struct {
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// InstanceUID 记录观察时的实例身份,不把同名新实例视为原目标。
|
||||
// +optional
|
||||
InstanceUID types.UID `json:"instanceUID,omitempty"`
|
||||
// Phase 暂不冻结供应子阶段枚举;它不是操作授权或绑定的替代记录。
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
// +kubebuilder:validation:XValidation:rule="!(has(oldSelf.spec.tenantRef) || (has(oldSelf.status) && has(oldSelf.status.instanceUID))) || (self.spec.instanceRef == oldSelf.spec.instanceRef && self.spec.database == oldSelf.spec.database && self.spec.loginRole == oldSelf.spec.loginRole && self.spec.source == oldSelf.spec.source && has(self.spec.credentialRef) == has(oldSelf.spec.credentialRef) && (!has(oldSelf.spec.credentialRef) || self.spec.credentialRef == oldSelf.spec.credentialRef))",message="managed database target cannot change after observation or binding starts"
|
||||
// +kubebuilder:printcolumn:name="Instance",type=string,JSONPath=`.spec.instanceRef.name`
|
||||
// +kubebuilder:printcolumn:name="Database",type=string,JSONPath=`.spec.database`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Ready')].status`
|
||||
type PostgreSQLDatabase struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||
Spec PostgreSQLDatabaseSpec `json:"spec"`
|
||||
// +optional
|
||||
Status PostgreSQLDatabaseStatus `json:"status,omitzero"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
type PostgreSQLDatabaseList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitzero"`
|
||||
Items []PostgreSQLDatabase `json:"items"`
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// PostgreSQLEndpoint 显式区分证书主机名与实际连接 IP,不进行 DNS 推导。
|
||||
type PostgreSQLEndpoint struct {
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
Host string `json:"host"`
|
||||
// +kubebuilder:validation:MaxLength=45
|
||||
// +kubebuilder:validation:XValidation:rule="isIP(self)",message="hostaddr must be a single IPv4 or IPv6 address"
|
||||
HostAddr string `json:"hostaddr"`
|
||||
// +kubebuilder:default=5432
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +kubebuilder:validation:Maximum=65535
|
||||
// +optional
|
||||
Port int32 `json:"port,omitempty"`
|
||||
// +kubebuilder:default=postgres
|
||||
// +optional
|
||||
Database PostgreSQLIdentifier `json:"database,omitempty"`
|
||||
// +kubebuilder:default=verify-full
|
||||
// +kubebuilder:validation:Enum=disable;require;verify-ca;verify-full
|
||||
// +optional
|
||||
SSLMode string `json:"sslMode,omitempty"`
|
||||
}
|
||||
|
||||
// AdminCredentialReference 只能读取 controller namespace 的 Secret。
|
||||
type AdminCredentialReference struct {
|
||||
Name ObjectName `json:"name"`
|
||||
// +kubebuilder:default=username
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +kubebuilder:validation:Pattern=`^[-._a-zA-Z0-9]+$`
|
||||
// +optional
|
||||
UsernameKey string `json:"usernameKey,omitempty"`
|
||||
// +kubebuilder:default=password
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +kubebuilder:validation:Pattern=`^[-._a-zA-Z0-9]+$`
|
||||
// +optional
|
||||
PasswordKey string `json:"passwordKey,omitempty"`
|
||||
}
|
||||
|
||||
type PostgreSQLInstanceSpec struct {
|
||||
Endpoint PostgreSQLEndpoint `json:"endpoint"`
|
||||
AdminCredentialRef AdminCredentialReference `json:"adminCredentialRef"`
|
||||
}
|
||||
|
||||
type PostgreSQLInstanceStatus struct {
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +kubebuilder:validation:Enum=Pending;Validating;Ready;Deleting
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
// +optional
|
||||
PostgreSQLVersion string `json:"postgresqlVersion,omitempty"`
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Ready')].status`
|
||||
type PostgreSQLInstance struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||
Spec PostgreSQLInstanceSpec `json:"spec"`
|
||||
// +optional
|
||||
Status PostgreSQLInstanceStatus `json:"status,omitzero"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
type PostgreSQLInstanceList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitzero"`
|
||||
Items []PostgreSQLInstance `json:"items"`
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// DatabaseProvisionRequest 仅用于动态申请,省略名称时由 controller 按 Tenant 名称解析。
|
||||
type DatabaseProvisionRequest struct {
|
||||
InstanceRef InstanceReference `json:"instanceRef"`
|
||||
// +optional
|
||||
Database PostgreSQLIdentifier `json:"database,omitempty"`
|
||||
// +optional
|
||||
LoginRole PostgreSQLIdentifier `json:"loginRole,omitempty"`
|
||||
}
|
||||
|
||||
// PostgreSQLTenantSpec 显式选择动态申请或已有 Database,不重复声明来源。
|
||||
// +kubebuilder:validation:XValidation:rule="has(self.provision) != has(self.databaseRef)",message="exactly one of provision and databaseRef is required"
|
||||
type PostgreSQLTenantSpec struct {
|
||||
// +optional
|
||||
Provision *DatabaseProvisionRequest `json:"provision,omitempty"`
|
||||
// +optional
|
||||
DatabaseRef *DatabaseReference `json:"databaseRef,omitempty"`
|
||||
// Extensions 保留后端扩展名称的原样拼写,不按 SQL identifier 限制。
|
||||
// +listType=set
|
||||
// +optional
|
||||
Extensions []string `json:"extensions,omitempty"`
|
||||
// SecretName 指定 Tenant namespace 内的投射目标,省略时使用合同约定的默认名称。
|
||||
// +optional
|
||||
SecretName ObjectName `json:"secretName,omitempty"`
|
||||
}
|
||||
|
||||
type PostgreSQLTenantStatus struct {
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// DatabaseRef 只有在资源侧确认绑定后才写入。
|
||||
// +optional
|
||||
DatabaseRef *BoundDatabaseReference `json:"databaseRef,omitempty"`
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
// SecretName 是已观察到的同 namespace 投射目标,不包含凭据值。
|
||||
// +optional
|
||||
SecretName ObjectName `json:"secretName,omitempty"`
|
||||
// CredentialURL 只含 OpenBao API 位置,禁止嵌入认证信息。
|
||||
// +optional
|
||||
CredentialURL string `json:"credentialURL,omitempty"`
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced
|
||||
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.status) || !has(oldSelf.status.phase) || !(oldSelf.status.phase in ['Binding', 'Bound', 'Deleting']) || ((has(self.spec.provision) == has(oldSelf.spec.provision)) && (!has(oldSelf.spec.provision) || self.spec.provision == oldSelf.spec.provision) && (has(self.spec.databaseRef) == has(oldSelf.spec.databaseRef)) && (!has(oldSelf.spec.databaseRef) || self.spec.databaseRef == oldSelf.spec.databaseRef))",message="binding target cannot change after binding starts"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.status) || !has(oldSelf.status.phase) || !(oldSelf.status.phase in ['Binding', 'Bound', 'Deleting']) || (has(self.status) && has(self.status.phase) && self.status.phase in ['Binding', 'Bound', 'Deleting'])",message="binding progress cannot return to an unbound state"
|
||||
// +kubebuilder:printcolumn:name="Database",type=string,JSONPath=`.status.databaseRef.name`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Ready')].status`
|
||||
type PostgreSQLTenant struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||
Spec PostgreSQLTenantSpec `json:"spec"`
|
||||
// +optional
|
||||
Status PostgreSQLTenantStatus `json:"status,omitzero"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
type PostgreSQLTenantList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitzero"`
|
||||
Items []PostgreSQLTenant `json:"items"`
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package v1alpha1_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
databasev1alpha1 "git.ddupan.top/panxiao81/ayatori/api/database/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
const (
|
||||
testNamespace = "database-api"
|
||||
testInstanceName = "shared-postgres"
|
||||
readyPhase = "Ready"
|
||||
)
|
||||
|
||||
func TestDatabaseAPI(t *testing.T) {
|
||||
if os.Getenv("KUBEBUILDER_ASSETS") == "" {
|
||||
t.Skip("KUBEBUILDER_ASSETS 未设置;运行 make test 执行真实 API server 测试")
|
||||
}
|
||||
scheme := runtime.NewScheme()
|
||||
if err := databasev1alpha1.AddToScheme(scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := corev1.AddToScheme(scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
crdPath, err := filepath.Abs("../../../config/crd/bases")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
environment := &envtest.Environment{CRDDirectoryPaths: []string{crdPath}, ErrorIfCRDPathMissing: true}
|
||||
config, err := environment.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("启动 envtest: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := environment.Stop(); err != nil {
|
||||
t.Errorf("停止 envtest: %v", err)
|
||||
}
|
||||
})
|
||||
client, err := ctrlclient.New(config, ctrlclient.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
namespace := &corev1.Namespace{}
|
||||
namespace.Name = testNamespace
|
||||
if err := client.Create(t.Context(), namespace); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Run("作用域和默认值", func(t *testing.T) { testDefaults(t, client) })
|
||||
t.Run("拒绝非法声明", func(t *testing.T) { testInvalidDeclarations(t, client) })
|
||||
t.Run("status隔离和绑定并发", func(t *testing.T) { testBindingWrites(t, client) })
|
||||
t.Run("仓库示例", func(t *testing.T) { testSamples(t, client, scheme) })
|
||||
}
|
||||
|
||||
func testSamples(t *testing.T, client ctrlclient.Client, scheme *runtime.Scheme) {
|
||||
paths := []string{
|
||||
"database_v1alpha1_postgresqlinstance.yaml",
|
||||
"database_v1alpha1_postgresqldatabase.yaml",
|
||||
"database_v1alpha1_postgresqltenant.yaml",
|
||||
}
|
||||
for _, name := range paths {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
file, err := os.Open(filepath.Join("../../../config/samples", name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := file.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(file, 4096)
|
||||
for {
|
||||
var raw runtime.RawExtension
|
||||
if err := decoder.Decode(&raw); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
object, _, err := serializer.NewCodecFactory(scheme).UniversalDeserializer().Decode(raw.Raw, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resource, ok := object.(ctrlclient.Object)
|
||||
if !ok {
|
||||
t.Fatalf("示例不是资源对象: %T", object)
|
||||
}
|
||||
if resource.GetNamespace() != "" {
|
||||
resource.SetNamespace(testNamespace)
|
||||
}
|
||||
if err := client.Create(t.Context(), resource); err != nil {
|
||||
t.Fatalf("示例未通过 API 校验: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaults(t *testing.T, client ctrlclient.Client) {
|
||||
instance := validInstance("defaults")
|
||||
if err := client.Create(t.Context(), instance); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
endpoint := instance.Spec.Endpoint
|
||||
if endpoint.Port != 5432 || endpoint.Database != "postgres" || endpoint.SSLMode != "verify-full" {
|
||||
t.Fatalf("连接默认值不符: %+v", endpoint)
|
||||
}
|
||||
credentials := instance.Spec.AdminCredentialRef
|
||||
if credentials.UsernameKey != "username" || credentials.PasswordKey != "password" {
|
||||
t.Fatal("管理 Secret 字段默认值不符")
|
||||
}
|
||||
database := validDatabase("defaults")
|
||||
if err := client.Create(t.Context(), database); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if database.Spec.ReclaimPolicy != databasev1alpha1.ReclaimRetain {
|
||||
t.Fatalf("默认回收策略 = %q", database.Spec.ReclaimPolicy)
|
||||
}
|
||||
// 进入删除流程前允许双向修改策略,不要求第二次审批字段。
|
||||
for _, policy := range []databasev1alpha1.ReclaimPolicy{databasev1alpha1.ReclaimDelete, databasev1alpha1.ReclaimRetain} {
|
||||
database.Spec.ReclaimPolicy = policy
|
||||
if err := client.Update(t.Context(), database); err != nil {
|
||||
t.Fatalf("修改回收策略: %v", err)
|
||||
}
|
||||
}
|
||||
objects := []struct {
|
||||
object ctrlclient.Object
|
||||
namespaced bool
|
||||
}{
|
||||
{instance, false}, {database, false}, {validTenant("scope"), true},
|
||||
}
|
||||
for _, item := range objects {
|
||||
namespaced, err := client.IsObjectNamespaced(item.object)
|
||||
if err != nil || namespaced != item.namespaced {
|
||||
t.Fatalf("%T 作用域 = %v, error = %v", item.object, namespaced, err)
|
||||
}
|
||||
}
|
||||
// 导入不要求 Tenant 或 Instance 对象已经存在,跨对象就绪由 controller 判断。
|
||||
imported := validDatabase("imported")
|
||||
imported.Spec.Source = "Import"
|
||||
imported.Spec.CredentialRef = &databasev1alpha1.CredentialReference{Mount: "secret", Path: "existing/app"}
|
||||
if err := client.Create(t.Context(), imported); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range []string{"first", "second"} {
|
||||
tenant := validTenant(name)
|
||||
tenant.Spec.Provision = nil
|
||||
tenant.Spec.DatabaseRef = &databasev1alpha1.DatabaseReference{Name: "imported"}
|
||||
if err := client.Create(t.Context(), tenant); err != nil {
|
||||
t.Fatalf("声明已有资源申请: %v", err)
|
||||
}
|
||||
}
|
||||
// 两个申请都可被 API 接受,不代表二者都已绑定或获得凭据。
|
||||
}
|
||||
|
||||
func testInvalidDeclarations(t *testing.T, client ctrlclient.Client) {
|
||||
instanceCases := []struct {
|
||||
name string
|
||||
mutate func(*databasev1alpha1.PostgreSQLInstance)
|
||||
}{
|
||||
{"port", func(i *databasev1alpha1.PostgreSQLInstance) { i.Spec.Endpoint.Port = -1 }},
|
||||
{"address", func(i *databasev1alpha1.PostgreSQLInstance) { i.Spec.Endpoint.HostAddr = "localhost" }},
|
||||
{"scoped-address", func(i *databasev1alpha1.PostgreSQLInstance) { i.Spec.Endpoint.HostAddr = "fe80::1%eth0" }},
|
||||
{"tls", func(i *databasev1alpha1.PostgreSQLInstance) { i.Spec.Endpoint.SSLMode = "prefer" }},
|
||||
{"identifier", func(i *databasev1alpha1.PostgreSQLInstance) { i.Spec.Endpoint.Database = "bad-name" }},
|
||||
{"secret-key", func(i *databasev1alpha1.PostgreSQLInstance) { i.Spec.AdminCredentialRef.PasswordKey = "bad/key" }},
|
||||
}
|
||||
for _, tc := range instanceCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
object := validInstance(tc.name)
|
||||
tc.mutate(object)
|
||||
requireInvalidCreate(t, client, object)
|
||||
})
|
||||
}
|
||||
databaseCases := []struct {
|
||||
name string
|
||||
mutate func(*databasev1alpha1.PostgreSQLDatabase)
|
||||
}{
|
||||
{"missing-instance", func(d *databasev1alpha1.PostgreSQLDatabase) { d.Spec.InstanceRef.Name = "" }},
|
||||
{"missing-role", func(d *databasev1alpha1.PostgreSQLDatabase) { d.Spec.LoginRole = "" }},
|
||||
{"unknown-source", func(d *databasev1alpha1.PostgreSQLDatabase) { d.Spec.Source = "Adopt" }},
|
||||
{"missing-credentials", func(d *databasev1alpha1.PostgreSQLDatabase) { d.Spec.Source = "Import" }},
|
||||
{"provision-credentials", func(d *databasev1alpha1.PostgreSQLDatabase) {
|
||||
d.Spec.CredentialRef = &databasev1alpha1.CredentialReference{Mount: "secret", Path: "existing"}
|
||||
}},
|
||||
{"unknown-policy", func(d *databasev1alpha1.PostgreSQLDatabase) { d.Spec.ReclaimPolicy = "Recycle" }},
|
||||
{"binding-without-uid", func(d *databasev1alpha1.PostgreSQLDatabase) {
|
||||
d.Spec.TenantRef = &databasev1alpha1.TenantReference{Namespace: testNamespace, Name: "tenant"}
|
||||
}},
|
||||
}
|
||||
for _, tc := range databaseCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
object := validDatabase(tc.name)
|
||||
tc.mutate(object)
|
||||
requireInvalidCreate(t, client, object)
|
||||
})
|
||||
}
|
||||
t.Run("互斥申请入口", func(t *testing.T) {
|
||||
tenant := validTenant("ambiguous")
|
||||
tenant.Spec.DatabaseRef = &databasev1alpha1.DatabaseReference{Name: "existing"}
|
||||
requireInvalidCreate(t, client, tenant)
|
||||
tenant.Spec.Provision = nil
|
||||
tenant.Spec.DatabaseRef = nil
|
||||
requireInvalidCreate(t, client, tenant)
|
||||
})
|
||||
}
|
||||
|
||||
// 本测试验证 API 写入语义,不模拟或宣称已经实现 controller 的恢复循环。
|
||||
func testBindingWrites(t *testing.T, client ctrlclient.Client) {
|
||||
ctx := t.Context()
|
||||
tenant := validTenant("binding")
|
||||
tenant.Status.Phase = readyPhase
|
||||
if err := client.Create(ctx, tenant); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tenant.Status.Phase != "" {
|
||||
t.Fatal("普通 Create 不应写入 status")
|
||||
}
|
||||
database := validDatabase("binding")
|
||||
if err := client.Create(ctx, database); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stale := database.DeepCopy()
|
||||
database.Spec.TenantRef = &databasev1alpha1.TenantReference{
|
||||
Namespace: tenant.Namespace, Name: databasev1alpha1.ObjectName(tenant.Name), UID: tenant.UID,
|
||||
}
|
||||
if err := client.Update(ctx, database); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stale.Spec.TenantRef = &databasev1alpha1.TenantReference{Namespace: tenant.Namespace, Name: "other", UID: "other-uid"}
|
||||
if err := client.Update(ctx, stale); !apierrors.IsConflict(err) {
|
||||
t.Fatalf("过期并发写入 = %v, want Conflict", err)
|
||||
}
|
||||
// 换用 API 回读的对象补第二步,证明恢复所需记录不依赖先前内存。
|
||||
observedDatabase := &databasev1alpha1.PostgreSQLDatabase{}
|
||||
if err := client.Get(ctx, ctrlclient.ObjectKeyFromObject(database), observedDatabase); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if observedDatabase.Spec.TenantRef.UID != tenant.UID {
|
||||
t.Fatal("资源侧绑定被竞争写入覆盖")
|
||||
}
|
||||
beforeGeneration := tenant.Generation
|
||||
tenant.Status.DatabaseRef = &databasev1alpha1.BoundDatabaseReference{
|
||||
Name: databasev1alpha1.ObjectName(database.Name), UID: database.UID,
|
||||
}
|
||||
if err := client.Status().Update(ctx, tenant); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tenant.Generation != beforeGeneration || tenant.Status.DatabaseRef.UID != database.UID {
|
||||
t.Fatal("status 更新错误地影响 generation 或绑定身份")
|
||||
}
|
||||
tenant.Status.Phase = readyPhase
|
||||
if err := client.Update(ctx, tenant); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tenant.Status.Phase != "" {
|
||||
t.Fatal("普通 Update 不应修改 status")
|
||||
}
|
||||
condition := metav1.Condition{Type: readyPhase, Status: metav1.ConditionFalse,
|
||||
Reason: "Pending", Message: "尚未验证后端", LastTransitionTime: metav1.Now()}
|
||||
tenant.Status.Conditions = []metav1.Condition{condition, condition}
|
||||
if err := client.Status().Update(ctx, tenant); !apierrors.IsInvalid(err) {
|
||||
t.Fatalf("重复 Condition = %v, want Invalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
func requireInvalidCreate(t *testing.T, client ctrlclient.Client, object ctrlclient.Object) {
|
||||
t.Helper()
|
||||
if err := client.Create(context.Background(), object); !apierrors.IsInvalid(err) {
|
||||
t.Fatalf("Create %T = %v, want Invalid", object, err)
|
||||
}
|
||||
}
|
||||
|
||||
func validInstance(name string) *databasev1alpha1.PostgreSQLInstance {
|
||||
object := &databasev1alpha1.PostgreSQLInstance{}
|
||||
object.Name = name
|
||||
object.Spec.Endpoint = databasev1alpha1.PostgreSQLEndpoint{Host: "postgres.example.test", HostAddr: "127.0.0.1"}
|
||||
object.Spec.AdminCredentialRef.Name = "postgres-admin"
|
||||
return object
|
||||
}
|
||||
|
||||
func validDatabase(name string) *databasev1alpha1.PostgreSQLDatabase {
|
||||
object := &databasev1alpha1.PostgreSQLDatabase{}
|
||||
object.Name = name
|
||||
object.Spec = databasev1alpha1.PostgreSQLDatabaseSpec{
|
||||
InstanceRef: databasev1alpha1.InstanceReference{Name: testInstanceName},
|
||||
Database: "app", LoginRole: "app", Source: "Provision",
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
func validTenant(name string) *databasev1alpha1.PostgreSQLTenant {
|
||||
object := &databasev1alpha1.PostgreSQLTenant{}
|
||||
object.Name = name
|
||||
object.Namespace = testNamespace
|
||||
object.Spec.Provision = &databasev1alpha1.DatabaseProvisionRequest{
|
||||
InstanceRef: databasev1alpha1.InstanceReference{Name: testInstanceName},
|
||||
}
|
||||
return object
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdminCredentialReference) DeepCopyInto(out *AdminCredentialReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdminCredentialReference.
|
||||
func (in *AdminCredentialReference) DeepCopy() *AdminCredentialReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdminCredentialReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BoundDatabaseReference) DeepCopyInto(out *BoundDatabaseReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundDatabaseReference.
|
||||
func (in *BoundDatabaseReference) DeepCopy() *BoundDatabaseReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BoundDatabaseReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CredentialReference) DeepCopyInto(out *CredentialReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialReference.
|
||||
func (in *CredentialReference) DeepCopy() *CredentialReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CredentialReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DatabaseProvisionRequest) DeepCopyInto(out *DatabaseProvisionRequest) {
|
||||
*out = *in
|
||||
out.InstanceRef = in.InstanceRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseProvisionRequest.
|
||||
func (in *DatabaseProvisionRequest) DeepCopy() *DatabaseProvisionRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DatabaseProvisionRequest)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DatabaseReference) DeepCopyInto(out *DatabaseReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseReference.
|
||||
func (in *DatabaseReference) DeepCopy() *DatabaseReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DatabaseReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InstanceReference) DeepCopyInto(out *InstanceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceReference.
|
||||
func (in *InstanceReference) DeepCopy() *InstanceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InstanceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLDatabase) DeepCopyInto(out *PostgreSQLDatabase) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLDatabase.
|
||||
func (in *PostgreSQLDatabase) DeepCopy() *PostgreSQLDatabase {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLDatabase)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PostgreSQLDatabase) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLDatabaseList) DeepCopyInto(out *PostgreSQLDatabaseList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]PostgreSQLDatabase, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLDatabaseList.
|
||||
func (in *PostgreSQLDatabaseList) DeepCopy() *PostgreSQLDatabaseList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLDatabaseList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PostgreSQLDatabaseList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLDatabaseSpec) DeepCopyInto(out *PostgreSQLDatabaseSpec) {
|
||||
*out = *in
|
||||
out.InstanceRef = in.InstanceRef
|
||||
if in.CredentialRef != nil {
|
||||
in, out := &in.CredentialRef, &out.CredentialRef
|
||||
*out = new(CredentialReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.TenantRef != nil {
|
||||
in, out := &in.TenantRef, &out.TenantRef
|
||||
*out = new(TenantReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLDatabaseSpec.
|
||||
func (in *PostgreSQLDatabaseSpec) DeepCopy() *PostgreSQLDatabaseSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLDatabaseSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLDatabaseStatus) DeepCopyInto(out *PostgreSQLDatabaseStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLDatabaseStatus.
|
||||
func (in *PostgreSQLDatabaseStatus) DeepCopy() *PostgreSQLDatabaseStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLDatabaseStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLEndpoint) DeepCopyInto(out *PostgreSQLEndpoint) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLEndpoint.
|
||||
func (in *PostgreSQLEndpoint) DeepCopy() *PostgreSQLEndpoint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLEndpoint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLInstance) DeepCopyInto(out *PostgreSQLInstance) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLInstance.
|
||||
func (in *PostgreSQLInstance) DeepCopy() *PostgreSQLInstance {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLInstance)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PostgreSQLInstance) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLInstanceList) DeepCopyInto(out *PostgreSQLInstanceList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]PostgreSQLInstance, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLInstanceList.
|
||||
func (in *PostgreSQLInstanceList) DeepCopy() *PostgreSQLInstanceList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLInstanceList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PostgreSQLInstanceList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLInstanceSpec) DeepCopyInto(out *PostgreSQLInstanceSpec) {
|
||||
*out = *in
|
||||
out.Endpoint = in.Endpoint
|
||||
out.AdminCredentialRef = in.AdminCredentialRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLInstanceSpec.
|
||||
func (in *PostgreSQLInstanceSpec) DeepCopy() *PostgreSQLInstanceSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLInstanceSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLInstanceStatus) DeepCopyInto(out *PostgreSQLInstanceStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLInstanceStatus.
|
||||
func (in *PostgreSQLInstanceStatus) DeepCopy() *PostgreSQLInstanceStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLInstanceStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLTenant) DeepCopyInto(out *PostgreSQLTenant) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLTenant.
|
||||
func (in *PostgreSQLTenant) DeepCopy() *PostgreSQLTenant {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLTenant)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PostgreSQLTenant) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLTenantList) DeepCopyInto(out *PostgreSQLTenantList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]PostgreSQLTenant, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLTenantList.
|
||||
func (in *PostgreSQLTenantList) DeepCopy() *PostgreSQLTenantList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLTenantList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PostgreSQLTenantList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLTenantSpec) DeepCopyInto(out *PostgreSQLTenantSpec) {
|
||||
*out = *in
|
||||
if in.Provision != nil {
|
||||
in, out := &in.Provision, &out.Provision
|
||||
*out = new(DatabaseProvisionRequest)
|
||||
**out = **in
|
||||
}
|
||||
if in.DatabaseRef != nil {
|
||||
in, out := &in.DatabaseRef, &out.DatabaseRef
|
||||
*out = new(DatabaseReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLTenantSpec.
|
||||
func (in *PostgreSQLTenantSpec) DeepCopy() *PostgreSQLTenantSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLTenantSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PostgreSQLTenantStatus) DeepCopyInto(out *PostgreSQLTenantStatus) {
|
||||
*out = *in
|
||||
if in.DatabaseRef != nil {
|
||||
in, out := &in.DatabaseRef, &out.DatabaseRef
|
||||
*out = new(BoundDatabaseReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLTenantStatus.
|
||||
func (in *PostgreSQLTenantStatus) DeepCopy() *PostgreSQLTenantStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PostgreSQLTenantStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantReference) DeepCopyInto(out *TenantReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantReference.
|
||||
func (in *TenantReference) DeepCopy() *TenantReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user