From d28c65948d766ac2b15973ab11564912db61ba19 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Thu, 16 May 2024 22:27:44 +0100 Subject: [PATCH 01/22] WIP refactoring to simplify generic kube client generation --- .gitignore | 3 +- internal/dbs/cockroach/k8s/clients.go | 42 ++++++---- internal/dbs/cockroach/k8s/dbs.go | 32 ++++--- internal/dbs/cockroach/k8s/migrations.go | 52 +++++++----- internal/dbs/cockroach/k8s/pvcs.go | 34 ++++---- internal/dbs/cockroach/k8s/pvcs_test.go | 4 +- internal/dbs/cockroach/k8s/secrets.go | 39 +++++---- internal/dbs/cockroach/k8s/services.go | 30 ++++--- internal/dbs/cockroach/k8s/stateful_sets.go | 46 +++++----- internal/dbs/nats/k8s/clients.go | 38 +++++---- internal/dbs/nats/k8s/dbs.go | 29 ++++--- pkg/k8s_generic/client.go | 93 +++++++++------------ 12 files changed, 245 insertions(+), 197 deletions(-) diff --git a/.gitignore b/.gitignore index a5ffee8..f1ab62d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .scratch dist -*.tgz \ No newline at end of file +*.tgz +.DS_Store diff --git a/internal/dbs/cockroach/k8s/clients.go b/internal/dbs/cockroach/k8s/clients.go index 207af4b..c34dae0 100644 --- a/internal/dbs/cockroach/k8s/clients.go +++ b/internal/dbs/cockroach/k8s/clients.go @@ -23,7 +23,7 @@ type CockroachClient struct { ResourceVersion string } -func (cli *CockroachClient) ToUnstructured() *unstructured.Unstructured { +func (cli CockroachClient) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", @@ -46,8 +46,9 @@ func (cli *CockroachClient) ToUnstructured() *unstructured.Unstructured { return result } -func (cli *CockroachClient) FromUnstructured(obj *unstructured.Unstructured) error { +func cockroachClientFromUnstructured(obj *unstructured.Unstructured) (CockroachClient, error) { var err error + cli := CockroachClient{} cli.Name = obj.GetName() cli.Namespace = obj.GetNamespace() cli.UID = string(obj.GetUID()) @@ -55,62 +56,66 @@ func (cli *CockroachClient) FromUnstructured(obj *unstructured.Unstructured) err cli.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") if err != nil { - return fmt.Errorf("failed to get db ref name: %+v", err) + return cli, fmt.Errorf("failed to get db ref name: %+v", err) } cli.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") if err != nil { - return fmt.Errorf("failed to get db ref namespace: %+v", err) + return cli, fmt.Errorf("failed to get db ref namespace: %+v", err) } cli.Database, err = k8s_generic.GetProperty[string](obj, "spec", "database") if err != nil { - return fmt.Errorf("failed to get database: %+v", err) + return cli, fmt.Errorf("failed to get database: %+v", err) } cli.Username, err = k8s_generic.GetProperty[string](obj, "spec", "username") if err != nil { - return fmt.Errorf("failed to get username: %+v", err) + return cli, fmt.Errorf("failed to get username: %+v", err) } cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") if err != nil { - return fmt.Errorf("failed to get secret: %+v", err) + return cli, fmt.Errorf("failed to get secret: %+v", err) } - return nil + return cli, nil } -func (cli *CockroachClient) GetName() string { +func (cli CockroachClient) GetName() string { return cli.Name } -func (cli *CockroachClient) GetNamespace() string { +func (cli CockroachClient) GetNamespace() string { return cli.Namespace } -func (cli *CockroachClient) GetUID() string { +func (cli CockroachClient) GetUID() string { return cli.UID } -func (cli *CockroachClient) GetResourceVersion() string { +func (cli CockroachClient) GetResourceVersion() string { return cli.ResourceVersion } -func (cli *CockroachClient) GetTarget() string { +func (cli CockroachClient) GetTarget() string { return cli.DBRef.Name } -func (cli *CockroachClient) GetTargetNamespace() string { +func (cli CockroachClient) GetTargetNamespace() string { return cli.DBRef.Namespace } -func (cli *CockroachClient) Equal(obj CockroachClient) bool { - return cli.CockroachClientComparable == obj.CockroachClientComparable +func (cli CockroachClient) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(CockroachClient); ok { + return cli.CockroachClientComparable == other.CockroachClientComparable + } + + return false } -func (c *Client) Clients() *k8s_generic.Client[CockroachClient, *CockroachClient] { - return k8s_generic.NewClient[CockroachClient]( +func (c *Client) Clients() *k8s_generic.Client[CockroachClient] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -119,5 +124,6 @@ func (c *Client) Clients() *k8s_generic.Client[CockroachClient, *CockroachClient }, "CockroachClient", nil, + cockroachClientFromUnstructured, ) } diff --git a/internal/dbs/cockroach/k8s/dbs.go b/internal/dbs/cockroach/k8s/dbs.go index 6402865..510a9cc 100644 --- a/internal/dbs/cockroach/k8s/dbs.go +++ b/internal/dbs/cockroach/k8s/dbs.go @@ -20,7 +20,7 @@ type CockroachDB struct { ResourceVersion string } -func (db *CockroachDB) ToUnstructured() *unstructured.Unstructured { +func (db CockroachDB) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", @@ -37,8 +37,9 @@ func (db *CockroachDB) ToUnstructured() *unstructured.Unstructured { return result } -func (db *CockroachDB) FromUnstructured(obj *unstructured.Unstructured) error { +func cockroachDBFromUnstructured(obj *unstructured.Unstructured) (CockroachDB, error) { var err error + db := CockroachDB{} db.Name = obj.GetName() db.Namespace = obj.GetNamespace() @@ -46,38 +47,42 @@ func (db *CockroachDB) FromUnstructured(obj *unstructured.Unstructured) error { db.ResourceVersion = obj.GetResourceVersion() db.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "storage") if err != nil { - return fmt.Errorf("failed to get storage: %+v", err) + return db, fmt.Errorf("failed to get storage: %+v", err) } - return nil + return db, nil } -func (db *CockroachDB) GetName() string { +func (db CockroachDB) GetName() string { return db.Name } -func (db *CockroachDB) GetNamespace() string { +func (db CockroachDB) GetNamespace() string { return db.Namespace } -func (db *CockroachDB) GetStorage() string { +func (db CockroachDB) GetStorage() string { return db.Storage } -func (db *CockroachDB) GetUID() string { +func (db CockroachDB) GetUID() string { return db.UID } -func (db *CockroachDB) GetResourceVersion() string { +func (db CockroachDB) GetResourceVersion() string { return db.ResourceVersion } -func (db *CockroachDB) Equal(obj CockroachDB) bool { - return db.CockroachDBComparable == obj.CockroachDBComparable +func (db CockroachDB) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(CockroachDB); ok { + return db.CockroachDBComparable == other.CockroachDBComparable + } + + return false } -func (c *Client) DBs() *k8s_generic.Client[CockroachDB, *CockroachDB] { - return k8s_generic.NewClient[CockroachDB]( +func (c *Client) DBs() *k8s_generic.Client[CockroachDB] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -86,5 +91,6 @@ func (c *Client) DBs() *k8s_generic.Client[CockroachDB, *CockroachDB] { }, "CockroachDB", nil, + cockroachDBFromUnstructured, ) } diff --git a/internal/dbs/cockroach/k8s/migrations.go b/internal/dbs/cockroach/k8s/migrations.go index 3f97de3..54245c1 100644 --- a/internal/dbs/cockroach/k8s/migrations.go +++ b/internal/dbs/cockroach/k8s/migrations.go @@ -23,31 +23,32 @@ type CockroachMigration struct { ResourceVersion string } -func (cm *CockroachMigration) ToUnstructured() *unstructured.Unstructured { +func (m CockroachMigration) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", "kind": "CockroachMigration", "metadata": map[string]interface{}{ - "name": cm.Name, - "namespace": cm.Namespace, + "name": m.Name, + "namespace": m.Namespace, }, "spec": map[string]interface{}{ "dbRef": map[string]interface{}{ - "name": cm.DBRef.Name, - "namespace": cm.DBRef.Namespace, + "name": m.DBRef.Name, + "namespace": m.DBRef.Namespace, }, - "database": cm.Database, - "migration": cm.Migration, - "index": cm.Index, + "database": m.Database, + "migration": m.Migration, + "index": m.Index, }, }) return result } -func (m *CockroachMigration) FromUnstructured(obj *unstructured.Unstructured) error { +func cockroachMigrationFromUnstructured(obj *unstructured.Unstructured) (CockroachMigration, error) { var err error + m := CockroachMigration{} m.Name = obj.GetName() m.Namespace = obj.GetNamespace() @@ -56,54 +57,58 @@ func (m *CockroachMigration) FromUnstructured(obj *unstructured.Unstructured) er m.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") if err != nil { - return fmt.Errorf("failed to get db ref name: %+v", err) + return m, fmt.Errorf("failed to get db ref name: %+v", err) } m.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") if err != nil { - return fmt.Errorf("failed to get db ref namespace: %+v", err) + return m, fmt.Errorf("failed to get db ref namespace: %+v", err) } m.Database, err = k8s_generic.GetProperty[string](obj, "spec", "database") if err != nil { - return fmt.Errorf("failed to get database: %+v", err) + return m, fmt.Errorf("failed to get database: %+v", err) } m.Migration, err = k8s_generic.GetProperty[string](obj, "spec", "migration") if err != nil { - return fmt.Errorf("failed to get migration: %+v", err) + return m, fmt.Errorf("failed to get migration: %+v", err) } m.Index, err = k8s_generic.GetProperty[int64](obj, "spec", "index") if err != nil { - return fmt.Errorf("failed to get index: %+v", err) + return m, fmt.Errorf("failed to get index: %+v", err) } - return nil + return m, nil } -func (m *CockroachMigration) GetName() string { +func (m CockroachMigration) GetName() string { return m.Name } -func (m *CockroachMigration) GetNamespace() string { +func (m CockroachMigration) GetNamespace() string { return m.Namespace } -func (m *CockroachMigration) GetUID() string { +func (m CockroachMigration) GetUID() string { return m.UID } -func (m *CockroachMigration) GetResourceVersion() string { +func (m CockroachMigration) GetResourceVersion() string { return m.ResourceVersion } -func (m *CockroachMigration) Equal(obj CockroachMigration) bool { - return m.CockroachMigrationComparable == obj.CockroachMigrationComparable +func (m CockroachMigration) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(*CockroachMigration); ok { + return m.CockroachMigrationComparable == other.CockroachMigrationComparable + } + + return false } -func (c *Client) Migrations() *k8s_generic.Client[CockroachMigration, *CockroachMigration] { - return k8s_generic.NewClient[CockroachMigration]( +func (c *Client) Migrations() *k8s_generic.Client[CockroachMigration] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -112,5 +117,6 @@ func (c *Client) Migrations() *k8s_generic.Client[CockroachMigration, *Cockroach }, "CockroachMigration", nil, + cockroachMigrationFromUnstructured, ) } diff --git a/internal/dbs/cockroach/k8s/pvcs.go b/internal/dbs/cockroach/k8s/pvcs.go index 40296c2..cb12567 100644 --- a/internal/dbs/cockroach/k8s/pvcs.go +++ b/internal/dbs/cockroach/k8s/pvcs.go @@ -22,13 +22,14 @@ type CockroachPVC struct { ResourceVersion string } -func (p *CockroachPVC) ToUnstructured() *unstructured.Unstructured { +func (p CockroachPVC) ToUnstructured() *unstructured.Unstructured { panic("not implemented") } -func (p *CockroachPVC) FromUnstructured(obj *unstructured.Unstructured) error { - var err error +func cockroachPVCFromUnstructured(obj *unstructured.Unstructured) (CockroachPVC, error) { + p := CockroachPVC{} + var err error p.Name = obj.GetName() p.Namespace = obj.GetNamespace() p.UID = string(obj.GetUID()) @@ -36,39 +37,43 @@ func (p *CockroachPVC) FromUnstructured(obj *unstructured.Unstructured) error { p.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "resources", "requests", "storage") if err != nil { - return fmt.Errorf("failed to get storage: %+v", err) + return p, fmt.Errorf("failed to get storage: %+v", err) } p.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") if err != nil { - return fmt.Errorf("failed to get database from app label: %+v", err) + return p, fmt.Errorf("failed to get database from app label: %+v", err) } - return nil + return p, nil } -func (p *CockroachPVC) GetName() string { +func (p CockroachPVC) GetName() string { return p.Name } -func (p *CockroachPVC) GetNamespace() string { +func (p CockroachPVC) GetNamespace() string { return p.Namespace } -func (p *CockroachPVC) GetUID() string { +func (p CockroachPVC) GetUID() string { return p.UID } -func (p *CockroachPVC) GetResourceVersion() string { +func (p CockroachPVC) GetResourceVersion() string { return p.ResourceVersion } -func (p *CockroachPVC) Equal(obj CockroachPVC) bool { - return p.CockroachPVCComparable == obj.CockroachPVCComparable +func (p CockroachPVC) Equal(obj k8s_generic.Resource) bool { + cockroachPVC, ok := obj.(*CockroachPVC) + if !ok { + return false + } + return p.CockroachPVCComparable == cockroachPVC.CockroachPVCComparable } -func (c *Client) PVCs() *k8s_generic.Client[CockroachPVC, *CockroachPVC] { - return k8s_generic.NewClient[CockroachPVC]( +func (c *Client) PVCs() *k8s_generic.Client[CockroachPVC] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -79,5 +84,6 @@ func (c *Client) PVCs() *k8s_generic.Client[CockroachPVC, *CockroachPVC] { k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "cockroachdb", }, common.LABEL_FILTERS), + cockroachPVCFromUnstructured, ) } diff --git a/internal/dbs/cockroach/k8s/pvcs_test.go b/internal/dbs/cockroach/k8s/pvcs_test.go index 6a45883..0fab152 100644 --- a/internal/dbs/cockroach/k8s/pvcs_test.go +++ b/internal/dbs/cockroach/k8s/pvcs_test.go @@ -30,8 +30,8 @@ func TestCockroachPVCFromUnstructured(t *testing.T) { }, } - cockroachPVC := &CockroachPVC{} - assert.NoError(t, cockroachPVC.FromUnstructured(pvc)) + cockroachPVC, err := cockroachPVCFromUnstructured(pvc) + assert.NoError(t, err) assert.Equal(t, "test-name", cockroachPVC.Name) assert.Equal(t, "test-namespace", cockroachPVC.Namespace) diff --git a/internal/dbs/cockroach/k8s/secrets.go b/internal/dbs/cockroach/k8s/secrets.go index 0615af4..08e6f34 100644 --- a/internal/dbs/cockroach/k8s/secrets.go +++ b/internal/dbs/cockroach/k8s/secrets.go @@ -29,15 +29,15 @@ func encode(data string) string { return base64.StdEncoding.EncodeToString([]byte(data)) } -func (s *CockroachSecret) GetHost() string { +func (s CockroachSecret) GetHost() string { return fmt.Sprintf("%s.%s.svc.cluster.local", s.DB.Name, s.DB.Namespace) } -func (s *CockroachSecret) GetPort() string { +func (s CockroachSecret) GetPort() string { return "26257" } -func (s *CockroachSecret) ToUnstructured() *unstructured.Unstructured { +func (s CockroachSecret) ToUnstructured() *unstructured.Unstructured { secret := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -62,7 +62,9 @@ func (s *CockroachSecret) ToUnstructured() *unstructured.Unstructured { return secret } -func (s *CockroachSecret) FromUnstructured(obj *unstructured.Unstructured) error { +func cockroachSecretFromUnstructured(obj *unstructured.Unstructured) (CockroachSecret, error) { + s := CockroachSecret{} + s.Name = obj.GetName() s.Namespace = obj.GetNamespace() @@ -71,46 +73,50 @@ func (s *CockroachSecret) FromUnstructured(obj *unstructured.Unstructured) error hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_HOST") if err != nil { - return fmt.Errorf("failed to get POSTGRES_HOST: %+v", err) + return s, fmt.Errorf("failed to get POSTGRES_HOST: %+v", err) } s.DB.Name = strings.Split(hostname, ".")[0] s.DB.Namespace = strings.Split(hostname, ".")[1] s.User, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_USER") if err != nil { - return fmt.Errorf("failed to get POSTGRES_USER: %+v", err) + return s, fmt.Errorf("failed to get POSTGRES_USER: %+v", err) } s.Database, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_NAME") if err != nil { - return fmt.Errorf("failed to get POSTGRES_NAME: %+v", err) + return s, fmt.Errorf("failed to get POSTGRES_NAME: %+v", err) } - return nil + return s, nil } -func (s *CockroachSecret) GetName() string { +func (s CockroachSecret) GetName() string { return s.Name } -func (s *CockroachSecret) GetNamespace() string { +func (s CockroachSecret) GetNamespace() string { return s.Namespace } -func (s *CockroachSecret) GetUID() string { +func (s CockroachSecret) GetUID() string { return s.UID } -func (s *CockroachSecret) GetResourceVersion() string { +func (s CockroachSecret) GetResourceVersion() string { return s.ResourceVersion } -func (s *CockroachSecret) Equal(obj CockroachSecret) bool { - return s.CockroachSecretComparable == obj.CockroachSecretComparable +func (s CockroachSecret) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(*CockroachSecret); ok { + return s.CockroachSecretComparable == other.CockroachSecretComparable + } + + return false } -func (c *Client) Secrets() *k8s_generic.Client[CockroachSecret, *CockroachSecret] { - return k8s_generic.NewClient[CockroachSecret]( +func (c *Client) Secrets() *k8s_generic.Client[CockroachSecret] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -121,5 +127,6 @@ func (c *Client) Secrets() *k8s_generic.Client[CockroachSecret, *CockroachSecret k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "cockroachdb", }, common.LABEL_FILTERS), + cockroachSecretFromUnstructured, ) } diff --git a/internal/dbs/cockroach/k8s/services.go b/internal/dbs/cockroach/k8s/services.go index 8f5f94f..ca2f540 100644 --- a/internal/dbs/cockroach/k8s/services.go +++ b/internal/dbs/cockroach/k8s/services.go @@ -18,7 +18,7 @@ type CockroachService struct { ResourceVersion string } -func (s *CockroachService) ToUnstructured() *unstructured.Unstructured { +func (s CockroachService) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -50,36 +50,43 @@ func (s *CockroachService) ToUnstructured() *unstructured.Unstructured { return statefulset } -func (s *CockroachService) FromUnstructured(obj *unstructured.Unstructured) error { +func cockroachServiceFromUnstructured(obj *unstructured.Unstructured) (CockroachService, error) { + s := CockroachService{} + s.Name = obj.GetName() s.Namespace = obj.GetNamespace() s.UID = string(obj.GetUID()) s.ResourceVersion = obj.GetResourceVersion() - return nil + + return s, nil } -func (s *CockroachService) GetName() string { +func (s CockroachService) GetName() string { return s.Name } -func (s *CockroachService) GetNamespace() string { +func (s CockroachService) GetNamespace() string { return s.Namespace } -func (s *CockroachService) GetUID() string { +func (s CockroachService) GetUID() string { return s.UID } -func (s *CockroachService) GetResourceVersion() string { +func (s CockroachService) GetResourceVersion() string { return s.ResourceVersion } -func (s *CockroachService) Equal(obj CockroachService) bool { - return s.CockroachServiceComparable == obj.CockroachServiceComparable +func (s CockroachService) Equal(obj k8s_generic.Resource) bool { + cockroachService, ok := obj.(*CockroachService) + if !ok { + return false + } + return s.CockroachServiceComparable == cockroachService.CockroachServiceComparable } -func (c *Client) Services() *k8s_generic.Client[CockroachService, *CockroachService] { - return k8s_generic.NewClient[CockroachService]( +func (c *Client) Services() *k8s_generic.Client[CockroachService] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -90,5 +97,6 @@ func (c *Client) Services() *k8s_generic.Client[CockroachService, *CockroachServ k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "cockroachdb", }, common.LABEL_FILTERS), + cockroachServiceFromUnstructured, ) } diff --git a/internal/dbs/cockroach/k8s/stateful_sets.go b/internal/dbs/cockroach/k8s/stateful_sets.go index 9170cc1..62f5bb4 100644 --- a/internal/dbs/cockroach/k8s/stateful_sets.go +++ b/internal/dbs/cockroach/k8s/stateful_sets.go @@ -22,7 +22,7 @@ type CockroachStatefulSet struct { ResourceVersion string } -func (s *CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { +func (s CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", @@ -137,8 +137,9 @@ func (s *CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { return statefulset } -func (s *CockroachStatefulSet) FromUnstructured(obj *unstructured.Unstructured) error { +func cockroachStatefulSetFromUnstructured(obj *unstructured.Unstructured) (CockroachStatefulSet, error) { var err error + s := CockroachStatefulSet{} s.Name = obj.GetName() s.Namespace = obj.GetNamespace() @@ -147,7 +148,7 @@ func (s *CockroachStatefulSet) FromUnstructured(obj *unstructured.Unstructured) s.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "volumeClaimTemplates", "0", "spec", "resources", "requests", "storage") if err != nil { - return fmt.Errorf("failed to get storage: %+v", err) + return s, fmt.Errorf("failed to get storage: %+v", err) } replicas, err := k8s_generic.GetProperty[int64](obj, "status", "replicas") @@ -162,39 +163,43 @@ func (s *CockroachStatefulSet) FromUnstructured(obj *unstructured.Unstructured) s.Ready = replicas > 0 && replicas == readyReplicas - return nil + return s, nil } -func (db *CockroachStatefulSet) GetName() string { - return db.Name +func (s CockroachStatefulSet) GetName() string { + return s.Name } -func (db *CockroachStatefulSet) GetNamespace() string { - return db.Namespace +func (s CockroachStatefulSet) GetNamespace() string { + return s.Namespace } -func (db *CockroachStatefulSet) GetUID() string { - return db.UID +func (s CockroachStatefulSet) GetUID() string { + return s.UID } -func (db *CockroachStatefulSet) GetResourceVersion() string { - return db.ResourceVersion +func (s CockroachStatefulSet) GetResourceVersion() string { + return s.ResourceVersion } -func (db *CockroachStatefulSet) GetStorage() string { - return db.Storage +func (s CockroachStatefulSet) GetStorage() string { + return s.Storage } -func (db *CockroachStatefulSet) IsReady() bool { - return db.Ready +func (s CockroachStatefulSet) IsReady() bool { + return s.Ready } -func (db *CockroachStatefulSet) Equal(obj CockroachStatefulSet) bool { - return db.CockroachStatefulSetComparable == obj.CockroachStatefulSetComparable +func (s CockroachStatefulSet) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(CockroachStatefulSet) + if !ok { + return false + } + return s.CockroachStatefulSetComparable == other.CockroachStatefulSetComparable } -func (c *Client) StatefulSets() *k8s_generic.Client[CockroachStatefulSet, *CockroachStatefulSet] { - return k8s_generic.NewClient[CockroachStatefulSet]( +func (c *Client) StatefulSets() *k8s_generic.Client[CockroachStatefulSet] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "apps", @@ -205,5 +210,6 @@ func (c *Client) StatefulSets() *k8s_generic.Client[CockroachStatefulSet, *Cockr k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "cockroachdb", }, common.LABEL_FILTERS), + cockroachStatefulSetFromUnstructured, ) } diff --git a/internal/dbs/nats/k8s/clients.go b/internal/dbs/nats/k8s/clients.go index 2e0f100..304068c 100644 --- a/internal/dbs/nats/k8s/clients.go +++ b/internal/dbs/nats/k8s/clients.go @@ -21,7 +21,7 @@ type NatsClient struct { ResourceVersion string } -func (cli *NatsClient) ToUnstructured() *unstructured.Unstructured { +func (cli NatsClient) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", @@ -42,8 +42,10 @@ func (cli *NatsClient) ToUnstructured() *unstructured.Unstructured { return result } -func (cli *NatsClient) FromUnstructured(obj *unstructured.Unstructured) error { +func natsClientFromUnstructured(obj *unstructured.Unstructured) (NatsClient, error) { var err error + cli := NatsClient{} + cli.Name = obj.GetName() cli.Namespace = obj.GetNamespace() cli.UID = string(obj.GetUID()) @@ -51,52 +53,55 @@ func (cli *NatsClient) FromUnstructured(obj *unstructured.Unstructured) error { cli.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") if err != nil { - return fmt.Errorf("failed to get db ref name: %+v", err) + return cli, fmt.Errorf("failed to get db ref name: %+v", err) } cli.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") if err != nil { - return fmt.Errorf("failed to get db ref namespace: %+v", err) + return cli, fmt.Errorf("failed to get db ref namespace: %+v", err) } cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") if err != nil { - return fmt.Errorf("failed to get secret: %+v", err) + return cli, fmt.Errorf("failed to get secret: %+v", err) } - return nil + return cli, nil } -func (cli *NatsClient) GetName() string { +func (cli NatsClient) GetName() string { return cli.Name } -func (cli *NatsClient) GetNamespace() string { +func (cli NatsClient) GetNamespace() string { return cli.Namespace } -func (cli *NatsClient) GetUID() string { +func (cli NatsClient) GetUID() string { return cli.UID } -func (cli *NatsClient) GetResourceVersion() string { +func (cli NatsClient) GetResourceVersion() string { return cli.ResourceVersion } -func (cli *NatsClient) GetTarget() string { +func (cli NatsClient) GetTarget() string { return cli.DBRef.Name } -func (cli *NatsClient) GetTargetNamespace() string { +func (cli NatsClient) GetTargetNamespace() string { return cli.DBRef.Namespace } -func (cli *NatsClient) Equal(obj NatsClient) bool { - return cli.NatsClientComparable == obj.NatsClientComparable +func (cli NatsClient) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(NatsClient); ok { + return cli.NatsClientComparable == other.NatsClientComparable + } + return false } -func (c *Client) Clients() *k8s_generic.Client[NatsClient, *NatsClient] { - return k8s_generic.NewClient[NatsClient]( +func (c *Client) Clients() *k8s_generic.Client[NatsClient] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -105,5 +110,6 @@ func (c *Client) Clients() *k8s_generic.Client[NatsClient, *NatsClient] { }, "NatsClient", nil, + natsClientFromUnstructured, ) } diff --git a/internal/dbs/nats/k8s/dbs.go b/internal/dbs/nats/k8s/dbs.go index 9a6fe1f..bd42ef3 100644 --- a/internal/dbs/nats/k8s/dbs.go +++ b/internal/dbs/nats/k8s/dbs.go @@ -17,7 +17,7 @@ type NatsDB struct { ResourceVersion string } -func (db *NatsDB) ToUnstructured() *unstructured.Unstructured { +func (db NatsDB) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", @@ -31,37 +31,43 @@ func (db *NatsDB) ToUnstructured() *unstructured.Unstructured { return result } -func (db *NatsDB) FromUnstructured(obj *unstructured.Unstructured) error { +func natsDBFromUnstructured(obj *unstructured.Unstructured) (NatsDB, error) { + db := NatsDB{} + db.Name = obj.GetName() db.Namespace = obj.GetNamespace() db.UID = string(obj.GetUID()) db.ResourceVersion = obj.GetResourceVersion() - return nil + return db, nil } -func (db *NatsDB) GetName() string { +func (db NatsDB) GetName() string { return db.Name } -func (db *NatsDB) GetNamespace() string { +func (db NatsDB) GetNamespace() string { return db.Namespace } -func (db *NatsDB) GetUID() string { +func (db NatsDB) GetUID() string { return db.UID } -func (db *NatsDB) GetResourceVersion() string { +func (db NatsDB) GetResourceVersion() string { return db.ResourceVersion } -func (db *NatsDB) Equal(obj NatsDB) bool { - return db.NatsDBComparable == obj.NatsDBComparable +func (db NatsDB) Equal(obj k8s_generic.Resource) bool { + natsDB, ok := obj.(*NatsDB) + if !ok { + return false + } + return db.NatsDBComparable == natsDB.NatsDBComparable } -func (c *Client) DBs() *k8s_generic.Client[NatsDB, *NatsDB] { - return k8s_generic.NewClient[NatsDB]( +func (c *Client) DBs() *k8s_generic.Client[NatsDB] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -70,5 +76,6 @@ func (c *Client) DBs() *k8s_generic.Client[NatsDB, *NatsDB] { }, "NatsDB", nil, + natsDBFromUnstructured, ) } diff --git a/pkg/k8s_generic/client.go b/pkg/k8s_generic/client.go index 6206e45..4e6123a 100644 --- a/pkg/k8s_generic/client.go +++ b/pkg/k8s_generic/client.go @@ -17,39 +17,39 @@ import ( "k8s.io/client-go/tools/cache" ) -type Resource[T any] interface { - *T +type Resource interface { GetName() string GetNamespace() string GetUID() string GetResourceVersion() string ToUnstructured() *unstructured.Unstructured - FromUnstructured(obj *unstructured.Unstructured) error - Equal(obj T) bool + Equal(obj Resource) bool } -type Client[T any, PT Resource[T]] struct { - client dynamic.Interface - restClient *rest.RESTClient - schema schema.GroupVersionResource - kind string - labelFilters map[string]string +type FromUnstructured[T Resource] func(obj *unstructured.Unstructured) (T, error) + +type Client[T Resource] struct { + client dynamic.Interface + restClient *rest.RESTClient + schema schema.GroupVersionResource + kind string + labelFilters map[string]string + fromUnstructured FromUnstructured[T] } -func NewClient[T any, PT Resource[T]](b *Builder, resourceSchema schema.GroupVersionResource, kind string, labelFilters map[string]string) *Client[T, PT] { - return &Client[T, PT]{ - schema: resourceSchema, - client: b.dynClient, - restClient: b.restClient, - labelFilters: labelFilters, - kind: kind, +func NewClient[T Resource](b *Builder, resourceSchema schema.GroupVersionResource, kind string, labelFilters map[string]string, fromUnstructured FromUnstructured[T]) *Client[T] { + return &Client[T]{ + schema: resourceSchema, + client: b.dynClient, + restClient: b.restClient, + labelFilters: labelFilters, + kind: kind, + fromUnstructured: fromUnstructured, } } -func (c *Client[T, PT]) Create(ctx context.Context, resource T) error { - ptr := PT(&resource) - - _, err := c.client.Resource(c.schema).Namespace(ptr.GetNamespace()).Create(ctx, ptr.ToUnstructured(), v1.CreateOptions{}) +func (c *Client[T]) Create(ctx context.Context, resource T) error { + _, err := c.client.Resource(c.schema).Namespace(resource.GetNamespace()).Create(ctx, resource.ToUnstructured(), v1.CreateOptions{}) if err != nil { return fmt.Errorf("failed to create %T: %+v", resource, err) } @@ -57,16 +57,14 @@ func (c *Client[T, PT]) Create(ctx context.Context, resource T) error { return nil } -func (c *Client[T, PT]) Get(ctx context.Context, name string, namespace string) (T, error) { - var object T - ptr := PT(&object) - +func (c *Client[T]) Get(ctx context.Context, name string, namespace string) (T, error) { res, err := c.client.Resource(c.schema).Namespace(namespace).Get(ctx, name, v1.GetOptions{}) if err != nil { + var object T return object, fmt.Errorf("failed to get %T: %s", object, name) } - err = ptr.FromUnstructured(res) + object, err := c.fromUnstructured(res) if err != nil { return object, fmt.Errorf("failed to parse %T: %+v", object, err) } @@ -75,7 +73,7 @@ func (c *Client[T, PT]) Get(ctx context.Context, name string, namespace string) } // function to get all resources -func (c *Client[T, PT]) GetAll(ctx context.Context) ([]T, error) { +func (c *Client[T]) GetAll(ctx context.Context) ([]T, error) { var objects []T options := v1.ListOptions{} if len(c.labelFilters) > 0 { @@ -90,10 +88,7 @@ func (c *Client[T, PT]) GetAll(ctx context.Context) ([]T, error) { } for _, item := range res.Items { - var object T - ptr := PT(&object) - - err = ptr.FromUnstructured(&item) + object, err := c.fromUnstructured(&item) if err != nil { return objects, fmt.Errorf("failed to parse %T: %+v", objects, err) } @@ -104,7 +99,7 @@ func (c *Client[T, PT]) GetAll(ctx context.Context) ([]T, error) { return objects, nil } -func (c *Client[T, PT]) Delete(ctx context.Context, name string, namespace string) error { +func (c *Client[T]) Delete(ctx context.Context, name string, namespace string) error { err := c.client.Resource(c.schema).Namespace(namespace).Delete(ctx, name, v1.DeleteOptions{}) if err != nil { return fmt.Errorf("failed to delete %T: %+v", name, err) @@ -113,7 +108,7 @@ func (c *Client[T, PT]) Delete(ctx context.Context, name string, namespace strin return nil } -func (c *Client[T, PT]) DeleteAll(ctx context.Context, namespace string) error { +func (c *Client[T]) DeleteAll(ctx context.Context, namespace string) error { err := c.client.Resource(c.schema).Namespace(namespace).DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{}) if err != nil { return fmt.Errorf("failed to delete all resources: %+v", err) @@ -122,12 +117,10 @@ func (c *Client[T, PT]) DeleteAll(ctx context.Context, namespace string) error { return nil } -func (c *Client[T, PT]) Update(ctx context.Context, resource T) error { - ptr := PT(&resource) - - _, err := c.client.Resource(c.schema).Namespace(ptr.GetNamespace()).Update(ctx, ptr.ToUnstructured(), v1.UpdateOptions{}) +func (c *Client[T]) Update(ctx context.Context, resource T) error { + _, err := c.client.Resource(c.schema).Namespace(resource.GetNamespace()).Update(ctx, resource.ToUnstructured(), v1.UpdateOptions{}) if err != nil { - return fmt.Errorf("failed up update resource %s: %+v", ptr.GetName(), err) + return fmt.Errorf("failed up update resource %s: %+v", resource.GetName(), err) } return nil @@ -138,11 +131,9 @@ type Update[T any] struct { ToRemove []T } -func (c *Client[T, PT]) Watch(ctx context.Context, cancel context.CancelFunc, updates chan<- any) error { +func (c *Client[T]) Watch(ctx context.Context, cancel context.CancelFunc, updates chan<- any) error { convert := func(obj interface{}) T { - var res T - ptr := PT(&res) - err := ptr.FromUnstructured(obj.(*unstructured.Unstructured)) + res, err := c.fromUnstructured(obj.(*unstructured.Unstructured)) if err != nil { log.Error().Err(err).Msgf("Failed to parse unstructured obj for %T", res) } @@ -173,7 +164,7 @@ func (c *Client[T, PT]) Watch(ctx context.Context, cancel context.CancelFunc, up oldRes := convert(oldObj) newRes := convert(newObj) - if !PT(&oldRes).Equal(newRes) { + if !oldRes.Equal(newRes) { updates <- Update[T]{ ToAdd: []T{newRes}, ToRemove: []T{oldRes}, @@ -200,16 +191,14 @@ func (c *Client[T, PT]) Watch(ctx context.Context, cancel context.CancelFunc, up return nil } -func (c *Client[T, PT]) Event(ctx context.Context, obj T, eventtype, reason, message string) { - ptr := PT(&obj) - +func (c *Client[T]) Event(ctx context.Context, obj T, eventtype, reason, message string) { ref := corev1.ObjectReference{ Kind: c.kind, APIVersion: c.schema.Group + "/" + c.schema.Version, - Name: ptr.GetName(), - Namespace: ptr.GetNamespace(), - UID: types.UID(ptr.GetUID()), - ResourceVersion: ptr.GetResourceVersion(), + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + UID: types.UID(obj.GetUID()), + ResourceVersion: obj.GetResourceVersion(), } t := v1.Time{Time: time.Now()} @@ -217,7 +206,7 @@ func (c *Client[T, PT]) Event(ctx context.Context, obj T, eventtype, reason, mes e := &corev1.Event{ ObjectMeta: v1.ObjectMeta{ Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()), - Namespace: ptr.GetNamespace(), + Namespace: obj.GetNamespace(), }, InvolvedObject: ref, Reason: reason, @@ -230,7 +219,7 @@ func (c *Client[T, PT]) Event(ctx context.Context, obj T, eventtype, reason, mes } err := c.restClient.Post(). - Namespace(ptr.GetNamespace()). + Namespace(obj.GetNamespace()). Resource("events"). Body(e). Do(ctx). From d42ef74d9972458abf4be11d91e24111d9934f32 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Fri, 17 May 2024 08:48:37 +0100 Subject: [PATCH 02/22] finished initial refactor --- internal/dbs/nats/k8s/deployments.go | 29 +++++++++-------- internal/dbs/nats/k8s/secrets.go | 33 ++++++++++++-------- internal/dbs/nats/k8s/services.go | 29 ++++++++++------- internal/dbs/redis/k8s/clients.go | 41 +++++++++++++++---------- internal/dbs/redis/k8s/dbs.go | 32 +++++++++++-------- internal/dbs/redis/k8s/pvcs.go | 32 +++++++++++-------- internal/dbs/redis/k8s/secrets.go | 37 +++++++++++++--------- internal/dbs/redis/k8s/services.go | 30 +++++++++++------- internal/dbs/redis/k8s/stateful_sets.go | 34 +++++++++++--------- 9 files changed, 178 insertions(+), 119 deletions(-) diff --git a/internal/dbs/nats/k8s/deployments.go b/internal/dbs/nats/k8s/deployments.go index 9e65d2b..809d67c 100644 --- a/internal/dbs/nats/k8s/deployments.go +++ b/internal/dbs/nats/k8s/deployments.go @@ -19,7 +19,7 @@ type NatsDeployment struct { ResourceVersion string } -func (d *NatsDeployment) ToUnstructured() *unstructured.Unstructured { +func (d NatsDeployment) ToUnstructured() *unstructured.Unstructured { deployment := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", @@ -96,8 +96,9 @@ func (d *NatsDeployment) ToUnstructured() *unstructured.Unstructured { return deployment } -func (d *NatsDeployment) FromUnstructured(obj *unstructured.Unstructured) error { +func natsDeploymentFromUnstructured(obj *unstructured.Unstructured) (NatsDeployment, error) { var err error + d := NatsDeployment{} d.Name = obj.GetName() d.Namespace = obj.GetNamespace() @@ -116,35 +117,38 @@ func (d *NatsDeployment) FromUnstructured(obj *unstructured.Unstructured) error d.Ready = replicas > 0 && replicas == readyReplicas - return nil + return d, nil } -func (d *NatsDeployment) GetName() string { +func (d NatsDeployment) GetName() string { return d.Name } -func (d *NatsDeployment) GetNamespace() string { +func (d NatsDeployment) GetNamespace() string { return d.Namespace } -func (d *NatsDeployment) GetUID() string { +func (d NatsDeployment) GetUID() string { return d.UID } -func (d *NatsDeployment) GetResourceVersion() string { +func (d NatsDeployment) GetResourceVersion() string { return d.ResourceVersion } -func (d *NatsDeployment) IsReady() bool { +func (d NatsDeployment) IsReady() bool { return d.Ready } -func (d *NatsDeployment) Equal(obj NatsDeployment) bool { - return d.NatsDeploymentComparable == obj.NatsDeploymentComparable +func (d NatsDeployment) Equal(obj k8s_generic.Resource) bool { + if natsDeployment, ok := obj.(*NatsDeployment); ok { + return d.NatsDeploymentComparable == natsDeployment.NatsDeploymentComparable + } + return false } -func (c *Client) Deployments() *k8s_generic.Client[NatsDeployment, *NatsDeployment] { - return k8s_generic.NewClient[NatsDeployment]( +func (c *Client) Deployments() *k8s_generic.Client[NatsDeployment] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "apps", @@ -155,5 +159,6 @@ func (c *Client) Deployments() *k8s_generic.Client[NatsDeployment, *NatsDeployme k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "nats", }, common.LABEL_FILTERS), + natsDeploymentFromUnstructured, ) } diff --git a/internal/dbs/nats/k8s/secrets.go b/internal/dbs/nats/k8s/secrets.go index c31304e..1867ffe 100644 --- a/internal/dbs/nats/k8s/secrets.go +++ b/internal/dbs/nats/k8s/secrets.go @@ -23,7 +23,7 @@ type NatsSecret struct { ResourceVersion string } -func (s *NatsSecret) GetHost() string { +func (s NatsSecret) GetHost() string { return fmt.Sprintf("%s.%s.svc.cluster.local", s.DB.Name, s.DB.Namespace) } @@ -31,7 +31,7 @@ func encode(data string) string { return base64.StdEncoding.EncodeToString([]byte(data)) } -func (s *NatsSecret) ToUnstructured() *unstructured.Unstructured { +func (s NatsSecret) ToUnstructured() *unstructured.Unstructured { secret := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -54,7 +54,9 @@ func (s *NatsSecret) ToUnstructured() *unstructured.Unstructured { return secret } -func (s *NatsSecret) FromUnstructured(obj *unstructured.Unstructured) error { +func natsSecretFromUnstructured(obj *unstructured.Unstructured) (NatsSecret, error) { + s := NatsSecret{} + s.Name = obj.GetName() s.Namespace = obj.GetNamespace() @@ -63,36 +65,40 @@ func (s *NatsSecret) FromUnstructured(obj *unstructured.Unstructured) error { hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "NATS_HOST") if err != nil { - return fmt.Errorf("failed to get NATS_HOST: %+v", err) + return s, fmt.Errorf("failed to get NATS_HOST: %+v", err) } s.DB.Name = strings.Split(hostname, ".")[0] s.DB.Namespace = strings.Split(hostname, ".")[1] - return nil + return s, nil } -func (s *NatsSecret) GetName() string { +func (s NatsSecret) GetName() string { return s.Name } -func (s *NatsSecret) GetNamespace() string { +func (s NatsSecret) GetNamespace() string { return s.Namespace } -func (s *NatsSecret) GetUID() string { +func (s NatsSecret) GetUID() string { return s.UID } -func (s *NatsSecret) GetResourceVersion() string { +func (s NatsSecret) GetResourceVersion() string { return s.ResourceVersion } -func (s *NatsSecret) Equal(obj NatsSecret) bool { - return s.NatsSecretComparable == obj.NatsSecretComparable +func (s NatsSecret) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(NatsSecret) + if !ok { + return false + } + return s.NatsSecretComparable == other.NatsSecretComparable } -func (c *Client) Secrets() *k8s_generic.Client[NatsSecret, *NatsSecret] { - return k8s_generic.NewClient[NatsSecret]( +func (c *Client) Secrets() *k8s_generic.Client[NatsSecret] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -103,5 +109,6 @@ func (c *Client) Secrets() *k8s_generic.Client[NatsSecret, *NatsSecret] { k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "nats", }, common.LABEL_FILTERS), + natsSecretFromUnstructured, ) } diff --git a/internal/dbs/nats/k8s/services.go b/internal/dbs/nats/k8s/services.go index 3c04e23..45ac628 100644 --- a/internal/dbs/nats/k8s/services.go +++ b/internal/dbs/nats/k8s/services.go @@ -18,7 +18,7 @@ type NatsService struct { ResourceVersion string } -func (s *NatsService) ToUnstructured() *unstructured.Unstructured { +func (s NatsService) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -50,36 +50,42 @@ func (s *NatsService) ToUnstructured() *unstructured.Unstructured { return statefulset } -func (s *NatsService) FromUnstructured(obj *unstructured.Unstructured) error { +func natsServiceFromUnstructured(obj *unstructured.Unstructured) (NatsService, error) { + s := NatsService{} + s.Name = obj.GetName() s.Namespace = obj.GetNamespace() s.UID = string(obj.GetUID()) s.ResourceVersion = obj.GetResourceVersion() - return nil + + return s, nil } -func (s *NatsService) GetName() string { +func (s NatsService) GetName() string { return s.Name } -func (s *NatsService) GetNamespace() string { +func (s NatsService) GetNamespace() string { return s.Namespace } -func (s *NatsService) GetUID() string { +func (s NatsService) GetUID() string { return s.UID } -func (s *NatsService) GetResourceVersion() string { +func (s NatsService) GetResourceVersion() string { return s.ResourceVersion } -func (s *NatsService) Equal(obj NatsService) bool { - return s.NatsServiceComparable == obj.NatsServiceComparable +func (s NatsService) Equal(obj k8s_generic.Resource) bool { + if natsService, ok := obj.(*NatsService); ok { + return s.NatsServiceComparable == natsService.NatsServiceComparable + } + return false } -func (c *Client) Services() *k8s_generic.Client[NatsService, *NatsService] { - return k8s_generic.NewClient[NatsService]( +func (c *Client) Services() *k8s_generic.Client[NatsService] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -90,5 +96,6 @@ func (c *Client) Services() *k8s_generic.Client[NatsService, *NatsService] { k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "nats", }, common.LABEL_FILTERS), + natsServiceFromUnstructured, ) } diff --git a/internal/dbs/redis/k8s/clients.go b/internal/dbs/redis/k8s/clients.go index 233619b..498fcf2 100644 --- a/internal/dbs/redis/k8s/clients.go +++ b/internal/dbs/redis/k8s/clients.go @@ -22,7 +22,7 @@ type RedisClient struct { ResourceVersion string } -func (cli *RedisClient) ToUnstructured() *unstructured.Unstructured { +func (cli RedisClient) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", @@ -44,8 +44,10 @@ func (cli *RedisClient) ToUnstructured() *unstructured.Unstructured { return result } -func (cli *RedisClient) FromUnstructured(obj *unstructured.Unstructured) error { +func redisClientFromUnstructured(obj *unstructured.Unstructured) (RedisClient, error) { var err error + cli := RedisClient{} + cli.Name = obj.GetName() cli.Namespace = obj.GetNamespace() cli.UID = string(obj.GetUID()) @@ -53,57 +55,61 @@ func (cli *RedisClient) FromUnstructured(obj *unstructured.Unstructured) error { cli.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") if err != nil { - return fmt.Errorf("failed to get db ref name: %+v", err) + return cli, fmt.Errorf("failed to get db ref name: %+v", err) } cli.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") if err != nil { - return fmt.Errorf("failed to get db ref namespace: %+v", err) + return cli, fmt.Errorf("failed to get db ref namespace: %+v", err) } cli.Unit, err = k8s_generic.GetProperty[int64](obj, "spec", "unit") if err != nil { - return fmt.Errorf("failed to get unit: %+v", err) + return cli, fmt.Errorf("failed to get unit: %+v", err) } cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") if err != nil { - return fmt.Errorf("failed to get secret: %+v", err) + return cli, fmt.Errorf("failed to get secret: %+v", err) } - return nil + return cli, nil } -func (cli *RedisClient) GetName() string { +func (cli RedisClient) GetName() string { return cli.Name } -func (cli *RedisClient) GetNamespace() string { +func (cli RedisClient) GetNamespace() string { return cli.Namespace } -func (cli *RedisClient) GetUID() string { +func (cli RedisClient) GetUID() string { return cli.UID } -func (cli *RedisClient) GetResourceVersion() string { +func (cli RedisClient) GetResourceVersion() string { return cli.ResourceVersion } -func (cli *RedisClient) GetTarget() string { +func (cli RedisClient) GetTarget() string { return cli.DBRef.Name } -func (cli *RedisClient) GetTargetNamespace() string { +func (cli RedisClient) GetTargetNamespace() string { return cli.DBRef.Namespace } -func (cli *RedisClient) Equal(obj RedisClient) bool { - return cli.RedisClientComparable == obj.RedisClientComparable +func (cli RedisClient) Equal(obj k8s_generic.Resource) bool { + redisObj, ok := obj.(*RedisClient) + if !ok { + return false + } + return cli.RedisClientComparable == redisObj.RedisClientComparable } -func (c *Client) Clients() *k8s_generic.Client[RedisClient, *RedisClient] { - return k8s_generic.NewClient[RedisClient]( +func (c *Client) Clients() *k8s_generic.Client[RedisClient] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -112,5 +118,6 @@ func (c *Client) Clients() *k8s_generic.Client[RedisClient, *RedisClient] { }, "RedisClient", nil, + redisClientFromUnstructured, ) } diff --git a/internal/dbs/redis/k8s/dbs.go b/internal/dbs/redis/k8s/dbs.go index f8ee8fb..a533188 100644 --- a/internal/dbs/redis/k8s/dbs.go +++ b/internal/dbs/redis/k8s/dbs.go @@ -20,7 +20,7 @@ type RedisDB struct { ResourceVersion string } -func (db *RedisDB) ToUnstructured() *unstructured.Unstructured { +func (db RedisDB) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", @@ -37,8 +37,9 @@ func (db *RedisDB) ToUnstructured() *unstructured.Unstructured { return result } -func (db *RedisDB) FromUnstructured(obj *unstructured.Unstructured) error { +func redisDBFromUnstructured(obj *unstructured.Unstructured) (RedisDB, error) { var err error + db := RedisDB{} db.Name = obj.GetName() db.Namespace = obj.GetNamespace() @@ -46,38 +47,42 @@ func (db *RedisDB) FromUnstructured(obj *unstructured.Unstructured) error { db.ResourceVersion = obj.GetResourceVersion() db.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "storage") if err != nil { - return fmt.Errorf("failed to get storage: %+v", err) + return db, fmt.Errorf("failed to get storage: %+v", err) } - return nil + return db, nil } -func (db *RedisDB) GetName() string { +func (db RedisDB) GetName() string { return db.Name } -func (db *RedisDB) GetNamespace() string { +func (db RedisDB) GetNamespace() string { return db.Namespace } -func (db *RedisDB) GetStorage() string { +func (db RedisDB) GetStorage() string { return db.Storage } -func (db *RedisDB) GetUID() string { +func (db RedisDB) GetUID() string { return db.UID } -func (db *RedisDB) GetResourceVersion() string { +func (db RedisDB) GetResourceVersion() string { return db.ResourceVersion } -func (db *RedisDB) Equal(obj RedisDB) bool { - return db.RedisDBComparable == obj.RedisDBComparable +func (db RedisDB) Equal(obj k8s_generic.Resource) bool { + redisDB, ok := obj.(*RedisDB) + if !ok { + return false + } + return db.RedisDBComparable == redisDB.RedisDBComparable } -func (c *Client) DBs() *k8s_generic.Client[RedisDB, *RedisDB] { - return k8s_generic.NewClient[RedisDB]( +func (c *Client) DBs() *k8s_generic.Client[RedisDB] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "ponglehub.co.uk", @@ -86,5 +91,6 @@ func (c *Client) DBs() *k8s_generic.Client[RedisDB, *RedisDB] { }, "RedisDB", nil, + redisDBFromUnstructured, ) } diff --git a/internal/dbs/redis/k8s/pvcs.go b/internal/dbs/redis/k8s/pvcs.go index 985f763..89a26f2 100644 --- a/internal/dbs/redis/k8s/pvcs.go +++ b/internal/dbs/redis/k8s/pvcs.go @@ -22,12 +22,13 @@ type RedisPVC struct { ResourceVersion string } -func (p *RedisPVC) ToUnstructured() *unstructured.Unstructured { +func (p RedisPVC) ToUnstructured() *unstructured.Unstructured { panic("not implemented") } -func (p *RedisPVC) FromUnstructured(obj *unstructured.Unstructured) error { +func redisPVCFromUnstructured(obj *unstructured.Unstructured) (RedisPVC, error) { var err error + p := RedisPVC{} p.Name = obj.GetName() p.Namespace = obj.GetNamespace() @@ -35,39 +36,43 @@ func (p *RedisPVC) FromUnstructured(obj *unstructured.Unstructured) error { p.ResourceVersion = obj.GetResourceVersion() p.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "resources", "requests", "storage") if err != nil { - return fmt.Errorf("failed to get storage: %+v", err) + return p, fmt.Errorf("failed to get storage: %+v", err) } p.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") if err != nil { - return fmt.Errorf("failed to get database from app label: %+v", err) + return p, fmt.Errorf("failed to get database from app label: %+v", err) } - return nil + return p, nil } -func (p *RedisPVC) GetName() string { +func (p RedisPVC) GetName() string { return p.Name } -func (p *RedisPVC) GetNamespace() string { +func (p RedisPVC) GetNamespace() string { return p.Namespace } -func (p *RedisPVC) GetUID() string { +func (p RedisPVC) GetUID() string { return p.UID } -func (p *RedisPVC) GetResourceVersion() string { +func (p RedisPVC) GetResourceVersion() string { return p.ResourceVersion } -func (p *RedisPVC) Equal(obj RedisPVC) bool { - return p.RedisPVCComparable == obj.RedisPVCComparable +func (p RedisPVC) Equal(obj k8s_generic.Resource) bool { + redisPVC, ok := obj.(*RedisPVC) + if !ok { + return false + } + return p.RedisPVCComparable == redisPVC.RedisPVCComparable } -func (c *Client) PVCs() *k8s_generic.Client[RedisPVC, *RedisPVC] { - return k8s_generic.NewClient[RedisPVC]( +func (c *Client) PVCs() *k8s_generic.Client[RedisPVC] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -78,5 +83,6 @@ func (c *Client) PVCs() *k8s_generic.Client[RedisPVC, *RedisPVC] { k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "redis", }, common.LABEL_FILTERS), + redisPVCFromUnstructured, ) } diff --git a/internal/dbs/redis/k8s/secrets.go b/internal/dbs/redis/k8s/secrets.go index 2df3dd8..d3483c7 100644 --- a/internal/dbs/redis/k8s/secrets.go +++ b/internal/dbs/redis/k8s/secrets.go @@ -25,7 +25,7 @@ type RedisSecret struct { ResourceVersion string } -func (s *RedisSecret) GetHost() string { +func (s RedisSecret) GetHost() string { return fmt.Sprintf("%s.%s.svc.cluster.local", s.DB.Name, s.DB.Namespace) } @@ -33,7 +33,7 @@ func encode(data string) string { return base64.StdEncoding.EncodeToString([]byte(data)) } -func (s *RedisSecret) ToUnstructured() *unstructured.Unstructured { +func (s RedisSecret) ToUnstructured() *unstructured.Unstructured { secret := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -57,7 +57,9 @@ func (s *RedisSecret) ToUnstructured() *unstructured.Unstructured { return secret } -func (s *RedisSecret) FromUnstructured(obj *unstructured.Unstructured) error { +func redisSecretFromUnstructured(obj *unstructured.Unstructured) (RedisSecret, error) { + s := RedisSecret{} + s.Name = obj.GetName() s.Namespace = obj.GetNamespace() @@ -66,46 +68,50 @@ func (s *RedisSecret) FromUnstructured(obj *unstructured.Unstructured) error { hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "REDIS_HOST") if err != nil { - return fmt.Errorf("failed to get REDIS_HOST: %+v", err) + return s, fmt.Errorf("failed to get REDIS_HOST: %+v", err) } s.DB.Name = strings.Split(hostname, ".")[0] s.DB.Namespace = strings.Split(hostname, ".")[1] unit, err := k8s_generic.GetEncodedProperty(obj, "data", "REDIS_UNIT") if err != nil { - return fmt.Errorf("failed to get REDIS_UNIT: %+v", err) + return s, fmt.Errorf("failed to get REDIS_UNIT: %+v", err) } s.Unit, err = strconv.Atoi(unit) if err != nil { - return fmt.Errorf("failed to parse REDIS_UNIT: %+v", err) + return s, fmt.Errorf("failed to parse REDIS_UNIT: %+v", err) } - return nil + return s, nil } -func (s *RedisSecret) GetName() string { +func (s RedisSecret) GetName() string { return s.Name } -func (s *RedisSecret) GetNamespace() string { +func (s RedisSecret) GetNamespace() string { return s.Namespace } -func (s *RedisSecret) GetUID() string { +func (s RedisSecret) GetUID() string { return s.UID } -func (s *RedisSecret) GetResourceVersion() string { +func (s RedisSecret) GetResourceVersion() string { return s.ResourceVersion } -func (s *RedisSecret) Equal(obj RedisSecret) bool { - return s.RedisSecretComparable == obj.RedisSecretComparable +func (s RedisSecret) Equal(obj k8s_generic.Resource) bool { + redisSecret, ok := obj.(*RedisSecret) + if !ok { + return false + } + return s.RedisSecretComparable == redisSecret.RedisSecretComparable } -func (c *Client) Secrets() *k8s_generic.Client[RedisSecret, *RedisSecret] { - return k8s_generic.NewClient[RedisSecret]( +func (c *Client) Secrets() *k8s_generic.Client[RedisSecret] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -116,5 +122,6 @@ func (c *Client) Secrets() *k8s_generic.Client[RedisSecret, *RedisSecret] { k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "redis", }, common.LABEL_FILTERS), + redisSecretFromUnstructured, ) } diff --git a/internal/dbs/redis/k8s/services.go b/internal/dbs/redis/k8s/services.go index 48e41f4..863fc35 100644 --- a/internal/dbs/redis/k8s/services.go +++ b/internal/dbs/redis/k8s/services.go @@ -18,7 +18,7 @@ type RedisService struct { ResourceVersion string } -func (s *RedisService) ToUnstructured() *unstructured.Unstructured { +func (s RedisService) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -50,36 +50,43 @@ func (s *RedisService) ToUnstructured() *unstructured.Unstructured { return statefulset } -func (s *RedisService) FromUnstructured(obj *unstructured.Unstructured) error { +func redisServiceFromUnstructured(obj *unstructured.Unstructured) (RedisService, error) { + s := RedisService{} + s.Name = obj.GetName() s.Namespace = obj.GetNamespace() s.UID = string(obj.GetUID()) s.ResourceVersion = obj.GetResourceVersion() - return nil + + return s, nil } -func (s *RedisService) GetName() string { +func (s RedisService) GetName() string { return s.Name } -func (s *RedisService) GetNamespace() string { +func (s RedisService) GetNamespace() string { return s.Namespace } -func (s *RedisService) GetUID() string { +func (s RedisService) GetUID() string { return s.UID } -func (s *RedisService) GetResourceVersion() string { +func (s RedisService) GetResourceVersion() string { return s.ResourceVersion } -func (s *RedisService) Equal(obj RedisService) bool { - return s.RedisServiceComparable == obj.RedisServiceComparable +func (s RedisService) Equal(obj k8s_generic.Resource) bool { + redisService, ok := obj.(*RedisService) + if !ok { + return false + } + return s.RedisServiceComparable == redisService.RedisServiceComparable } -func (c *Client) Services() *k8s_generic.Client[RedisService, *RedisService] { - return k8s_generic.NewClient[RedisService]( +func (c *Client) Services() *k8s_generic.Client[RedisService] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "", @@ -90,5 +97,6 @@ func (c *Client) Services() *k8s_generic.Client[RedisService, *RedisService] { k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "redis", }, common.LABEL_FILTERS), + redisServiceFromUnstructured, ) } diff --git a/internal/dbs/redis/k8s/stateful_sets.go b/internal/dbs/redis/k8s/stateful_sets.go index 6ecd0d0..e443517 100644 --- a/internal/dbs/redis/k8s/stateful_sets.go +++ b/internal/dbs/redis/k8s/stateful_sets.go @@ -22,7 +22,7 @@ type RedisStatefulSet struct { ResourceVersion string } -func (s *RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { +func (s RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", @@ -125,8 +125,9 @@ func (s *RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { return statefulset } -func (s *RedisStatefulSet) FromUnstructured(obj *unstructured.Unstructured) error { +func redisStatefulSetFromUnstructured(obj *unstructured.Unstructured) (RedisStatefulSet, error) { var err error + s := RedisStatefulSet{} s.Name = obj.GetName() s.Namespace = obj.GetNamespace() @@ -135,7 +136,7 @@ func (s *RedisStatefulSet) FromUnstructured(obj *unstructured.Unstructured) erro s.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "volumeClaimTemplates", "0", "spec", "resources", "requests", "storage") if err != nil { - return fmt.Errorf("failed to get storage: %+v", err) + return s, fmt.Errorf("failed to get storage: %+v", err) } replicas, err := k8s_generic.GetProperty[int64](obj, "status", "replicas") @@ -150,39 +151,43 @@ func (s *RedisStatefulSet) FromUnstructured(obj *unstructured.Unstructured) erro s.Ready = replicas > 0 && replicas == readyReplicas - return nil + return s, nil } -func (db *RedisStatefulSet) GetName() string { +func (db RedisStatefulSet) GetName() string { return db.Name } -func (db *RedisStatefulSet) GetNamespace() string { +func (db RedisStatefulSet) GetNamespace() string { return db.Namespace } -func (db *RedisStatefulSet) GetUID() string { +func (db RedisStatefulSet) GetUID() string { return db.UID } -func (db *RedisStatefulSet) GetResourceVersion() string { +func (db RedisStatefulSet) GetResourceVersion() string { return db.ResourceVersion } -func (db *RedisStatefulSet) GetStorage() string { +func (db RedisStatefulSet) GetStorage() string { return db.Storage } -func (db *RedisStatefulSet) IsReady() bool { +func (db RedisStatefulSet) IsReady() bool { return db.Ready } -func (db *RedisStatefulSet) Equal(obj RedisStatefulSet) bool { - return db.RedisStatefulSetComparable == obj.RedisStatefulSetComparable +func (db RedisStatefulSet) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*RedisStatefulSet) + if !ok { + return false + } + return db.RedisStatefulSetComparable == other.RedisStatefulSetComparable } -func (c *Client) StatefulSets() *k8s_generic.Client[RedisStatefulSet, *RedisStatefulSet] { - return k8s_generic.NewClient[RedisStatefulSet]( +func (c *Client) StatefulSets() *k8s_generic.Client[RedisStatefulSet] { + return k8s_generic.NewClient( c.builder, schema.GroupVersionResource{ Group: "apps", @@ -193,5 +198,6 @@ func (c *Client) StatefulSets() *k8s_generic.Client[RedisStatefulSet, *RedisStat k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "redis", }, common.LABEL_FILTERS), + redisStatefulSetFromUnstructured, ) } From 8af3cd305c85cede6be78c37890f9bbb885d937c Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Fri, 17 May 2024 18:02:04 +0100 Subject: [PATCH 03/22] about to make a bigger mess --- .../chart/crds/cockroach-migration.crd.yaml | 52 ------- ...ient.crd.yaml => postgres-client.crd.yaml} | 14 +- ...roach-db.crd.yaml => postgres-db.crd.yaml} | 14 +- internal/dbs/cockroach/k8s/client.go | 31 ---- internal/dbs/cockroach/k8s/clients.go | 129 ----------------- internal/dbs/cockroach/k8s/db_ref.go | 6 - internal/dbs/cockroach/k8s/dbs.go | 96 ------------- internal/dbs/cockroach/k8s/migrations.go | 122 ---------------- internal/dbs/cockroach/k8s/pvcs.go | 89 ------------ internal/dbs/cockroach/k8s/secrets.go | 132 ----------------- internal/dbs/cockroach/k8s/services.go | 102 ------------- internal/dbs/nats/k8s/client.go | 9 -- .../database/client.go | 0 .../database/migrations.go | 0 .../{cockroach => postgres}/database/types.go | 0 internal/dbs/postgres/k8s/client.go | 64 +++++++++ internal/dbs/postgres/k8s/clients/clients.go | 130 +++++++++++++++++ .../dbs/postgres/k8s/clusters/clusters.go | 92 ++++++++++++ internal/dbs/postgres/k8s/pvcs/pvcs.go | 86 +++++++++++ .../k8s => postgres/k8s/pvcs}/pvcs_test.go | 4 +- internal/dbs/postgres/k8s/secrets/secrets.go | 134 ++++++++++++++++++ .../k8s/secrets}/secrets_test.go | 6 +- .../dbs/postgres/k8s/services/services.go | 99 +++++++++++++ .../k8s/stateful_sets}/stateful_sets.go | 103 +++++++------- .../managers/database/consolidator.go | 0 .../managers/database/manager.go | 15 +- .../managers/database/state.go | 4 +- .../managers/deployment/manager.go | 0 .../managers/deployment/state.go | 0 internal/dbs/redis/k8s/client.go | 9 -- internal/runtime/runtime.go | 20 +-- pkg/k8s_generic/client.go | 17 ++- 32 files changed, 706 insertions(+), 873 deletions(-) delete mode 100644 deploy/chart/crds/cockroach-migration.crd.yaml rename deploy/chart/crds/{cockroach-client.crd.yaml => postgres-client.crd.yaml} (87%) rename deploy/chart/crds/{cockroach-db.crd.yaml => postgres-db.crd.yaml} (76%) delete mode 100644 internal/dbs/cockroach/k8s/client.go delete mode 100644 internal/dbs/cockroach/k8s/clients.go delete mode 100644 internal/dbs/cockroach/k8s/db_ref.go delete mode 100644 internal/dbs/cockroach/k8s/dbs.go delete mode 100644 internal/dbs/cockroach/k8s/migrations.go delete mode 100644 internal/dbs/cockroach/k8s/pvcs.go delete mode 100644 internal/dbs/cockroach/k8s/secrets.go delete mode 100644 internal/dbs/cockroach/k8s/services.go rename internal/dbs/{cockroach => postgres}/database/client.go (100%) rename internal/dbs/{cockroach => postgres}/database/migrations.go (100%) rename internal/dbs/{cockroach => postgres}/database/types.go (100%) create mode 100644 internal/dbs/postgres/k8s/client.go create mode 100644 internal/dbs/postgres/k8s/clients/clients.go create mode 100644 internal/dbs/postgres/k8s/clusters/clusters.go create mode 100644 internal/dbs/postgres/k8s/pvcs/pvcs.go rename internal/dbs/{cockroach/k8s => postgres/k8s/pvcs}/pvcs_test.go (94%) create mode 100644 internal/dbs/postgres/k8s/secrets/secrets.go rename internal/dbs/{cockroach/k8s => postgres/k8s/secrets}/secrets_test.go (93%) create mode 100644 internal/dbs/postgres/k8s/services/services.go rename internal/dbs/{cockroach/k8s => postgres/k8s/stateful_sets}/stateful_sets.go (64%) rename internal/dbs/{cockroach => postgres}/managers/database/consolidator.go (100%) rename internal/dbs/{cockroach => postgres}/managers/database/manager.go (94%) rename internal/dbs/{cockroach => postgres}/managers/database/state.go (96%) rename internal/dbs/{cockroach => postgres}/managers/deployment/manager.go (100%) rename internal/dbs/{cockroach => postgres}/managers/deployment/state.go (100%) diff --git a/deploy/chart/crds/cockroach-migration.crd.yaml b/deploy/chart/crds/cockroach-migration.crd.yaml deleted file mode 100644 index d570b20..0000000 --- a/deploy/chart/crds/cockroach-migration.crd.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: cockroachmigrations.ponglehub.co.uk -spec: - group: ponglehub.co.uk - versions: - - name: v1alpha1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - dbRef: - type: object - properties: - name: - type: string - namespace: - type: string - required: [ name, namespace ] - database: - type: string - migration: - type: string - index: - type: integer - required: [ dbRef, database, migration, index ] - status: - type: object - properties: - applied: - type: boolean - subresources: - status: {} - additionalPrinterColumns: - - name: Applied - type: boolean - description: Reports whether the migration has been run - jsonPath: .status.applied - scope: Namespaced - names: - plural: cockroachmigrations - singular: cockroachmigration - kind: CockroachMigration - shortNames: - - cm - - cms \ No newline at end of file diff --git a/deploy/chart/crds/cockroach-client.crd.yaml b/deploy/chart/crds/postgres-client.crd.yaml similarity index 87% rename from deploy/chart/crds/cockroach-client.crd.yaml rename to deploy/chart/crds/postgres-client.crd.yaml index 754d275..d83a932 100644 --- a/deploy/chart/crds/cockroach-client.crd.yaml +++ b/deploy/chart/crds/postgres-client.crd.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: cockroachclients.ponglehub.co.uk + name: postgresclients.ponglehub.co.uk spec: group: ponglehub.co.uk versions: @@ -23,7 +23,7 @@ spec: namespace: type: string required: [ name, namespace ] - database: + cluster: type: string username: type: string @@ -44,9 +44,9 @@ spec: jsonPath: .status.ready scope: Namespaced names: - plural: cockroachclients - singular: cockroachclient - kind: CockroachClient + plural: postgresclients + singular: postgresclient + kind: PostgresClient shortNames: - - cc - - ccs \ No newline at end of file + - pgcl + - pgcls \ No newline at end of file diff --git a/deploy/chart/crds/cockroach-db.crd.yaml b/deploy/chart/crds/postgres-db.crd.yaml similarity index 76% rename from deploy/chart/crds/cockroach-db.crd.yaml rename to deploy/chart/crds/postgres-db.crd.yaml index 5630a44..ed26168 100644 --- a/deploy/chart/crds/cockroach-db.crd.yaml +++ b/deploy/chart/crds/postgres-db.crd.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: cockroachdbs.ponglehub.co.uk + name: postgresclusters.ponglehub.co.uk spec: group: ponglehub.co.uk versions: @@ -27,13 +27,13 @@ spec: additionalPrinterColumns: - name: Ready type: boolean - description: Defines whether the database is running + description: Defines whether the database cluster is running jsonPath: .status.ready scope: Namespaced names: - plural: cockroachdbs - singular: cockroachdb - kind: CockroachDB + plural: postgresclusters + singular: postgresclusters + kind: PostgresCluster shortNames: - - cdb - - cdbs \ No newline at end of file + - pgc + - pgcs \ No newline at end of file diff --git a/internal/dbs/cockroach/k8s/client.go b/internal/dbs/cockroach/k8s/client.go deleted file mode 100644 index d0f322c..0000000 --- a/internal/dbs/cockroach/k8s/client.go +++ /dev/null @@ -1,31 +0,0 @@ -package k8s - -import ( - "context" - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" -) - -type K8sClient[T any] interface { - Watch(ctx context.Context, cancel context.CancelFunc) (<-chan k8s_generic.Update[T], error) - Create(ctx context.Context, resource T) error - Delete(ctx context.Context, name string, namespace string) error - Update(ctx context.Context, resource T) error - Event(ctx context.Context, obj T, eventtype, reason, message string) -} - -type Client struct { - builder *k8s_generic.Builder -} - -func New() (*Client, error) { - builder, err := k8s_generic.NewBuilder() - if err != nil { - return nil, fmt.Errorf("failed to create k8s builder: %+v", err) - } - - return &Client{ - builder: builder, - }, nil -} diff --git a/internal/dbs/cockroach/k8s/clients.go b/internal/dbs/cockroach/k8s/clients.go deleted file mode 100644 index c34dae0..0000000 --- a/internal/dbs/cockroach/k8s/clients.go +++ /dev/null @@ -1,129 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CockroachClientComparable struct { - Name string - Namespace string - DBRef DBRef - Database string - Username string - Secret string -} - -type CockroachClient struct { - CockroachClientComparable - UID string - ResourceVersion string -} - -func (cli CockroachClient) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "CockroachClient", - "metadata": map[string]interface{}{ - "name": cli.Name, - "namespace": cli.Namespace, - }, - "spec": map[string]interface{}{ - "dbRef": map[string]interface{}{ - "name": cli.DBRef.Name, - "namespace": cli.DBRef.Namespace, - }, - "database": cli.Database, - "username": cli.Username, - "secret": cli.Secret, - }, - }) - - return result -} - -func cockroachClientFromUnstructured(obj *unstructured.Unstructured) (CockroachClient, error) { - var err error - cli := CockroachClient{} - cli.Name = obj.GetName() - cli.Namespace = obj.GetNamespace() - cli.UID = string(obj.GetUID()) - cli.ResourceVersion = obj.GetResourceVersion() - - cli.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") - if err != nil { - return cli, fmt.Errorf("failed to get db ref name: %+v", err) - } - - cli.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") - if err != nil { - return cli, fmt.Errorf("failed to get db ref namespace: %+v", err) - } - - cli.Database, err = k8s_generic.GetProperty[string](obj, "spec", "database") - if err != nil { - return cli, fmt.Errorf("failed to get database: %+v", err) - } - - cli.Username, err = k8s_generic.GetProperty[string](obj, "spec", "username") - if err != nil { - return cli, fmt.Errorf("failed to get username: %+v", err) - } - - cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") - if err != nil { - return cli, fmt.Errorf("failed to get secret: %+v", err) - } - - return cli, nil -} - -func (cli CockroachClient) GetName() string { - return cli.Name -} - -func (cli CockroachClient) GetNamespace() string { - return cli.Namespace -} - -func (cli CockroachClient) GetUID() string { - return cli.UID -} - -func (cli CockroachClient) GetResourceVersion() string { - return cli.ResourceVersion -} - -func (cli CockroachClient) GetTarget() string { - return cli.DBRef.Name -} - -func (cli CockroachClient) GetTargetNamespace() string { - return cli.DBRef.Namespace -} - -func (cli CockroachClient) Equal(obj k8s_generic.Resource) bool { - if other, ok := obj.(CockroachClient); ok { - return cli.CockroachClientComparable == other.CockroachClientComparable - } - - return false -} - -func (c *Client) Clients() *k8s_generic.Client[CockroachClient] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "cockroachclients", - }, - "CockroachClient", - nil, - cockroachClientFromUnstructured, - ) -} diff --git a/internal/dbs/cockroach/k8s/db_ref.go b/internal/dbs/cockroach/k8s/db_ref.go deleted file mode 100644 index 396a672..0000000 --- a/internal/dbs/cockroach/k8s/db_ref.go +++ /dev/null @@ -1,6 +0,0 @@ -package k8s - -type DBRef struct { - Name string - Namespace string -} diff --git a/internal/dbs/cockroach/k8s/dbs.go b/internal/dbs/cockroach/k8s/dbs.go deleted file mode 100644 index 510a9cc..0000000 --- a/internal/dbs/cockroach/k8s/dbs.go +++ /dev/null @@ -1,96 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CockroachDBComparable struct { - Name string - Namespace string - Storage string -} - -type CockroachDB struct { - CockroachDBComparable - UID string - ResourceVersion string -} - -func (db CockroachDB) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "CockroachDB", - "metadata": map[string]interface{}{ - "name": db.Name, - "namespace": db.Namespace, - }, - "spec": map[string]interface{}{ - "storage": db.Storage, - }, - }) - - return result -} - -func cockroachDBFromUnstructured(obj *unstructured.Unstructured) (CockroachDB, error) { - var err error - db := CockroachDB{} - - db.Name = obj.GetName() - db.Namespace = obj.GetNamespace() - db.UID = string(obj.GetUID()) - db.ResourceVersion = obj.GetResourceVersion() - db.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "storage") - if err != nil { - return db, fmt.Errorf("failed to get storage: %+v", err) - } - - return db, nil -} - -func (db CockroachDB) GetName() string { - return db.Name -} - -func (db CockroachDB) GetNamespace() string { - return db.Namespace -} - -func (db CockroachDB) GetStorage() string { - return db.Storage -} - -func (db CockroachDB) GetUID() string { - return db.UID -} - -func (db CockroachDB) GetResourceVersion() string { - return db.ResourceVersion -} - -func (db CockroachDB) Equal(obj k8s_generic.Resource) bool { - if other, ok := obj.(CockroachDB); ok { - return db.CockroachDBComparable == other.CockroachDBComparable - } - - return false -} - -func (c *Client) DBs() *k8s_generic.Client[CockroachDB] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "cockroachdbs", - }, - "CockroachDB", - nil, - cockroachDBFromUnstructured, - ) -} diff --git a/internal/dbs/cockroach/k8s/migrations.go b/internal/dbs/cockroach/k8s/migrations.go deleted file mode 100644 index 54245c1..0000000 --- a/internal/dbs/cockroach/k8s/migrations.go +++ /dev/null @@ -1,122 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CockroachMigrationComparable struct { - Name string - Namespace string - DBRef DBRef - Database string - Migration string - Index int64 -} - -type CockroachMigration struct { - CockroachMigrationComparable - UID string - ResourceVersion string -} - -func (m CockroachMigration) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "CockroachMigration", - "metadata": map[string]interface{}{ - "name": m.Name, - "namespace": m.Namespace, - }, - "spec": map[string]interface{}{ - "dbRef": map[string]interface{}{ - "name": m.DBRef.Name, - "namespace": m.DBRef.Namespace, - }, - "database": m.Database, - "migration": m.Migration, - "index": m.Index, - }, - }) - - return result -} - -func cockroachMigrationFromUnstructured(obj *unstructured.Unstructured) (CockroachMigration, error) { - var err error - m := CockroachMigration{} - - m.Name = obj.GetName() - m.Namespace = obj.GetNamespace() - m.UID = string(obj.GetUID()) - m.ResourceVersion = obj.GetResourceVersion() - - m.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") - if err != nil { - return m, fmt.Errorf("failed to get db ref name: %+v", err) - } - - m.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") - if err != nil { - return m, fmt.Errorf("failed to get db ref namespace: %+v", err) - } - - m.Database, err = k8s_generic.GetProperty[string](obj, "spec", "database") - if err != nil { - return m, fmt.Errorf("failed to get database: %+v", err) - } - - m.Migration, err = k8s_generic.GetProperty[string](obj, "spec", "migration") - if err != nil { - return m, fmt.Errorf("failed to get migration: %+v", err) - } - - m.Index, err = k8s_generic.GetProperty[int64](obj, "spec", "index") - if err != nil { - return m, fmt.Errorf("failed to get index: %+v", err) - } - - return m, nil -} - -func (m CockroachMigration) GetName() string { - return m.Name -} - -func (m CockroachMigration) GetNamespace() string { - return m.Namespace -} - -func (m CockroachMigration) GetUID() string { - return m.UID -} - -func (m CockroachMigration) GetResourceVersion() string { - return m.ResourceVersion -} - -func (m CockroachMigration) Equal(obj k8s_generic.Resource) bool { - if other, ok := obj.(*CockroachMigration); ok { - return m.CockroachMigrationComparable == other.CockroachMigrationComparable - } - - return false -} - -func (c *Client) Migrations() *k8s_generic.Client[CockroachMigration] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "cockroachmigrations", - }, - "CockroachMigration", - nil, - cockroachMigrationFromUnstructured, - ) -} diff --git a/internal/dbs/cockroach/k8s/pvcs.go b/internal/dbs/cockroach/k8s/pvcs.go deleted file mode 100644 index cb12567..0000000 --- a/internal/dbs/cockroach/k8s/pvcs.go +++ /dev/null @@ -1,89 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CockroachPVCComparable struct { - Name string - Namespace string - Database string - Storage string -} - -type CockroachPVC struct { - CockroachPVCComparable - UID string - ResourceVersion string -} - -func (p CockroachPVC) ToUnstructured() *unstructured.Unstructured { - panic("not implemented") -} - -func cockroachPVCFromUnstructured(obj *unstructured.Unstructured) (CockroachPVC, error) { - p := CockroachPVC{} - - var err error - p.Name = obj.GetName() - p.Namespace = obj.GetNamespace() - p.UID = string(obj.GetUID()) - p.ResourceVersion = obj.GetResourceVersion() - - p.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "resources", "requests", "storage") - if err != nil { - return p, fmt.Errorf("failed to get storage: %+v", err) - } - - p.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") - if err != nil { - return p, fmt.Errorf("failed to get database from app label: %+v", err) - } - - return p, nil -} - -func (p CockroachPVC) GetName() string { - return p.Name -} - -func (p CockroachPVC) GetNamespace() string { - return p.Namespace -} - -func (p CockroachPVC) GetUID() string { - return p.UID -} - -func (p CockroachPVC) GetResourceVersion() string { - return p.ResourceVersion -} - -func (p CockroachPVC) Equal(obj k8s_generic.Resource) bool { - cockroachPVC, ok := obj.(*CockroachPVC) - if !ok { - return false - } - return p.CockroachPVCComparable == cockroachPVC.CockroachPVCComparable -} - -func (c *Client) PVCs() *k8s_generic.Client[CockroachPVC] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "persistentvolumeclaims", - }, - "PersistentVolumeClaim", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "cockroachdb", - }, common.LABEL_FILTERS), - cockroachPVCFromUnstructured, - ) -} diff --git a/internal/dbs/cockroach/k8s/secrets.go b/internal/dbs/cockroach/k8s/secrets.go deleted file mode 100644 index 08e6f34..0000000 --- a/internal/dbs/cockroach/k8s/secrets.go +++ /dev/null @@ -1,132 +0,0 @@ -package k8s - -import ( - "encoding/base64" - "fmt" - "strings" - - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CockroachSecretComparable struct { - Name string - Namespace string - DB DBRef - Database string - User string -} - -type CockroachSecret struct { - CockroachSecretComparable - UID string - ResourceVersion string -} - -func encode(data string) string { - return base64.StdEncoding.EncodeToString([]byte(data)) -} - -func (s CockroachSecret) GetHost() string { - return fmt.Sprintf("%s.%s.svc.cluster.local", s.DB.Name, s.DB.Namespace) -} - -func (s CockroachSecret) GetPort() string { - return "26257" -} - -func (s CockroachSecret) ToUnstructured() *unstructured.Unstructured { - secret := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": s.Name, - "labels": k8s_generic.Merge(map[string]string{ - "app": s.Name, - "ponglehub.co.uk/resource-type": "cockroachdb", - }, common.LABEL_FILTERS), - "namespace": s.Namespace, - }, - "data": map[string]interface{}{ - "POSTGRES_HOST": encode(s.GetHost()), - "POSTGRES_PORT": encode(s.GetPort()), - "POSTGRES_USER": encode(s.User), - "POSTGRES_NAME": encode(s.Database), - }, - }, - } - - return secret -} - -func cockroachSecretFromUnstructured(obj *unstructured.Unstructured) (CockroachSecret, error) { - s := CockroachSecret{} - - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() - - hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_HOST") - if err != nil { - return s, fmt.Errorf("failed to get POSTGRES_HOST: %+v", err) - } - s.DB.Name = strings.Split(hostname, ".")[0] - s.DB.Namespace = strings.Split(hostname, ".")[1] - - s.User, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_USER") - if err != nil { - return s, fmt.Errorf("failed to get POSTGRES_USER: %+v", err) - } - - s.Database, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_NAME") - if err != nil { - return s, fmt.Errorf("failed to get POSTGRES_NAME: %+v", err) - } - - return s, nil -} - -func (s CockroachSecret) GetName() string { - return s.Name -} - -func (s CockroachSecret) GetNamespace() string { - return s.Namespace -} - -func (s CockroachSecret) GetUID() string { - return s.UID -} - -func (s CockroachSecret) GetResourceVersion() string { - return s.ResourceVersion -} - -func (s CockroachSecret) Equal(obj k8s_generic.Resource) bool { - if other, ok := obj.(*CockroachSecret); ok { - return s.CockroachSecretComparable == other.CockroachSecretComparable - } - - return false -} - -func (c *Client) Secrets() *k8s_generic.Client[CockroachSecret] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "secrets", - }, - "Secret", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "cockroachdb", - }, common.LABEL_FILTERS), - cockroachSecretFromUnstructured, - ) -} diff --git a/internal/dbs/cockroach/k8s/services.go b/internal/dbs/cockroach/k8s/services.go deleted file mode 100644 index ca2f540..0000000 --- a/internal/dbs/cockroach/k8s/services.go +++ /dev/null @@ -1,102 +0,0 @@ -package k8s - -import ( - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CockroachServiceComparable struct { - Name string - Namespace string -} - -type CockroachService struct { - CockroachServiceComparable - UID string - ResourceVersion string -} - -func (s CockroachService) ToUnstructured() *unstructured.Unstructured { - statefulset := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Service", - "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, - "labels": k8s_generic.Merge(map[string]string{ - "app": s.Name, - "ponglehub.co.uk/resource-type": "cockroachdb", - }, common.LABEL_FILTERS), - }, - "spec": map[string]interface{}{ - "ports": []map[string]interface{}{ - { - "name": "grpc", - "port": 26257, - "protocol": "TCP", - "targetPort": "grpc", - }, - }, - "selector": map[string]interface{}{ - "app": s.Name, - }, - }, - }, - } - - return statefulset -} - -func cockroachServiceFromUnstructured(obj *unstructured.Unstructured) (CockroachService, error) { - s := CockroachService{} - - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() - - return s, nil -} - -func (s CockroachService) GetName() string { - return s.Name -} - -func (s CockroachService) GetNamespace() string { - return s.Namespace -} - -func (s CockroachService) GetUID() string { - return s.UID -} - -func (s CockroachService) GetResourceVersion() string { - return s.ResourceVersion -} - -func (s CockroachService) Equal(obj k8s_generic.Resource) bool { - cockroachService, ok := obj.(*CockroachService) - if !ok { - return false - } - return s.CockroachServiceComparable == cockroachService.CockroachServiceComparable -} - -func (c *Client) Services() *k8s_generic.Client[CockroachService] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "services", - }, - "Service", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "cockroachdb", - }, common.LABEL_FILTERS), - cockroachServiceFromUnstructured, - ) -} diff --git a/internal/dbs/nats/k8s/client.go b/internal/dbs/nats/k8s/client.go index d0f322c..2e641cc 100644 --- a/internal/dbs/nats/k8s/client.go +++ b/internal/dbs/nats/k8s/client.go @@ -1,20 +1,11 @@ package k8s import ( - "context" "fmt" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" ) -type K8sClient[T any] interface { - Watch(ctx context.Context, cancel context.CancelFunc) (<-chan k8s_generic.Update[T], error) - Create(ctx context.Context, resource T) error - Delete(ctx context.Context, name string, namespace string) error - Update(ctx context.Context, resource T) error - Event(ctx context.Context, obj T, eventtype, reason, message string) -} - type Client struct { builder *k8s_generic.Builder } diff --git a/internal/dbs/cockroach/database/client.go b/internal/dbs/postgres/database/client.go similarity index 100% rename from internal/dbs/cockroach/database/client.go rename to internal/dbs/postgres/database/client.go diff --git a/internal/dbs/cockroach/database/migrations.go b/internal/dbs/postgres/database/migrations.go similarity index 100% rename from internal/dbs/cockroach/database/migrations.go rename to internal/dbs/postgres/database/migrations.go diff --git a/internal/dbs/cockroach/database/types.go b/internal/dbs/postgres/database/types.go similarity index 100% rename from internal/dbs/cockroach/database/types.go rename to internal/dbs/postgres/database/types.go diff --git a/internal/dbs/postgres/k8s/client.go b/internal/dbs/postgres/k8s/client.go new file mode 100644 index 0000000..5bba343 --- /dev/null +++ b/internal/dbs/postgres/k8s/client.go @@ -0,0 +1,64 @@ +package k8s + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/pvcs" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/services" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" +) + +type Client struct { + builder *k8s_generic.Builder + clients *k8s_generic.Client[clients.Resource] + clusters *k8s_generic.Client[clusters.Resource] + pvcs *k8s_generic.Client[pvcs.Resource] + secrets *k8s_generic.Client[secrets.Resource] + services *k8s_generic.Client[services.Resource] + statefulsets *k8s_generic.Client[stateful_sets.Resource] +} + +func New() (*Client, error) { + builder, err := k8s_generic.NewBuilder() + if err != nil { + return nil, fmt.Errorf("failed to create k8s builder: %+v", err) + } + + return &Client{ + builder: builder, + clients: k8s_generic.NewClient(builder, clients.ClientArgs), + clusters: k8s_generic.NewClient(builder, clusters.ClientArgs), + pvcs: k8s_generic.NewClient(builder, pvcs.ClientArgs), + secrets: k8s_generic.NewClient(builder, secrets.ClientArgs), + services: k8s_generic.NewClient(builder, services.ClientArgs), + statefulsets: k8s_generic.NewClient(builder, stateful_sets.ClientArgs), + }, nil +} + +func (c *Client) Clients() *k8s_generic.Client[clients.Resource] { + return c.clients +} + +func (c *Client) Clusters() *k8s_generic.Client[clusters.Resource] { + return c.clusters +} + +func (c *Client) PVCs() *k8s_generic.Client[pvcs.Resource] { + return c.pvcs +} + +func (c *Client) Secrets() *k8s_generic.Client[secrets.Resource] { + return c.secrets +} + +func (c *Client) Services() *k8s_generic.Client[services.Resource] { + return c.services +} + +func (c *Client) StatefulSets() *k8s_generic.Client[stateful_sets.Resource] { + return c.statefulsets +} diff --git a/internal/dbs/postgres/k8s/clients/clients.go b/internal/dbs/postgres/k8s/clients/clients.go new file mode 100644 index 0000000..3ec8804 --- /dev/null +++ b/internal/dbs/postgres/k8s/clients/clients.go @@ -0,0 +1,130 @@ +package clients + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "ponglehub.co.uk", + Version: "v1alpha1", + Resource: "postgresclients", + }, + Kind: "PostgresClient", + FromUnstructured: fromUnstructured, +} + +type DBRef struct { + Name string + Namespace string +} + +type Comparable struct { + Name string + Namespace string + DBRef DBRef + Cluster string + Username string + Secret string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + result := &unstructured.Unstructured{} + result.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "ponglehub.co.uk/v1alpha1", + "kind": "PostgresClient", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + }, + "spec": map[string]interface{}{ + "dbRef": map[string]interface{}{ + "name": r.DBRef.Name, + "namespace": r.DBRef.Namespace, + }, + "cluster": r.Cluster, + "username": r.Username, + "secret": r.Secret, + }, + }) + + return result +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + var err error + r := Resource{} + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + r.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") + if err != nil { + return r, fmt.Errorf("failed to get db ref name: %+v", err) + } + + r.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") + if err != nil { + return r, fmt.Errorf("failed to get db ref namespace: %+v", err) + } + + r.Cluster, err = k8s_generic.GetProperty[string](obj, "spec", "cluster") + if err != nil { + return r, fmt.Errorf("failed to get cluster: %+v", err) + } + + r.Username, err = k8s_generic.GetProperty[string](obj, "spec", "username") + if err != nil { + return r, fmt.Errorf("failed to get username: %+v", err) + } + + r.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") + if err != nil { + return r, fmt.Errorf("failed to get secret: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) GetTarget() string { + return r.DBRef.Name +} + +func (r Resource) GetTargetNamespace() string { + return r.DBRef.Namespace +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(Resource); ok { + return r.Comparable == other.Comparable + } + + return false +} diff --git a/internal/dbs/postgres/k8s/clusters/clusters.go b/internal/dbs/postgres/k8s/clusters/clusters.go new file mode 100644 index 0000000..baed853 --- /dev/null +++ b/internal/dbs/postgres/k8s/clusters/clusters.go @@ -0,0 +1,92 @@ +package clusters + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "ponglehub.co.uk", + Version: "v1alpha1", + Resource: "postgresclusters", + }, + Kind: "Cluster", + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string + Storage string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + result := &unstructured.Unstructured{} + result.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "ponglehub.co.uk/v1alpha1", + "kind": "Cluster", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + }, + "spec": map[string]interface{}{ + "storage": r.Storage, + }, + }) + + return result +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + var err error + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + r.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "storage") + if err != nil { + return r, fmt.Errorf("failed to get storage: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetStorage() string { + return r.Storage +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(Resource); ok { + return r.Comparable == other.Comparable + } + + return false +} diff --git a/internal/dbs/postgres/k8s/pvcs/pvcs.go b/internal/dbs/postgres/k8s/pvcs/pvcs.go new file mode 100644 index 0000000..68a8341 --- /dev/null +++ b/internal/dbs/postgres/k8s/pvcs/pvcs.go @@ -0,0 +1,86 @@ +package pvcs + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "persistentvolumeclaims", + }, + Kind: "PersistentVolumeClaim", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "postgrescluster", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string + Database string + Storage string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + panic("not implemented") +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + var err error + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + r.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "resources", "requests", "storage") + if err != nil { + return r, fmt.Errorf("failed to get storage: %+v", err) + } + + r.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") + if err != nil { + return r, fmt.Errorf("failed to get database from app label: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + cockroachPVC, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == cockroachPVC.Comparable +} diff --git a/internal/dbs/cockroach/k8s/pvcs_test.go b/internal/dbs/postgres/k8s/pvcs/pvcs_test.go similarity index 94% rename from internal/dbs/cockroach/k8s/pvcs_test.go rename to internal/dbs/postgres/k8s/pvcs/pvcs_test.go index 0fab152..20571de 100644 --- a/internal/dbs/cockroach/k8s/pvcs_test.go +++ b/internal/dbs/postgres/k8s/pvcs/pvcs_test.go @@ -1,4 +1,4 @@ -package k8s +package pvcs import ( "testing" @@ -30,7 +30,7 @@ func TestCockroachPVCFromUnstructured(t *testing.T) { }, } - cockroachPVC, err := cockroachPVCFromUnstructured(pvc) + cockroachPVC, err := fromUnstructured(pvc) assert.NoError(t, err) assert.Equal(t, "test-name", cockroachPVC.Name) diff --git a/internal/dbs/postgres/k8s/secrets/secrets.go b/internal/dbs/postgres/k8s/secrets/secrets.go new file mode 100644 index 0000000..9c689e2 --- /dev/null +++ b/internal/dbs/postgres/k8s/secrets/secrets.go @@ -0,0 +1,134 @@ +package secrets + +import ( + "encoding/base64" + "fmt" + "strings" + + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "secrets", + }, + Kind: "Secret", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "postgrescluster", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type DBRef struct { + Name string + Namespace string +} + +type Comparable struct { + Name string + Namespace string + DB DBRef + Database string + User string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func encode(data string) string { + return base64.StdEncoding.EncodeToString([]byte(data)) +} + +func (r Resource) GetHost() string { + return fmt.Sprintf("%s.%s.svc.cluster.local", r.DB.Name, r.DB.Namespace) +} + +func (r Resource) GetPort() string { + return "26257" +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": r.Name, + "labels": k8s_generic.Merge(map[string]string{ + "app": r.Name, + "ponglehub.co.uk/resource-type": "postgrescluster", + }, common.LABEL_FILTERS), + "namespace": r.Namespace, + }, + "data": map[string]interface{}{ + "POSTGRES_HOST": encode(r.GetHost()), + "POSTGRES_PORT": encode(r.GetPort()), + "POSTGRES_USER": encode(r.User), + "POSTGRES_NAME": encode(r.Database), + }, + }, + } + + return secret +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_HOST") + if err != nil { + return r, fmt.Errorf("failed to get POSTGRES_HOST: %+v", err) + } + r.DB.Name = strings.Split(hostname, ".")[0] + r.DB.Namespace = strings.Split(hostname, ".")[1] + + r.User, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_USER") + if err != nil { + return r, fmt.Errorf("failed to get POSTGRES_USER: %+v", err) + } + + r.Database, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_NAME") + if err != nil { + return r, fmt.Errorf("failed to get POSTGRES_NAME: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(*Resource); ok { + return r.Comparable == other.Comparable + } + + return false +} diff --git a/internal/dbs/cockroach/k8s/secrets_test.go b/internal/dbs/postgres/k8s/secrets/secrets_test.go similarity index 93% rename from internal/dbs/cockroach/k8s/secrets_test.go rename to internal/dbs/postgres/k8s/secrets/secrets_test.go index f8ee792..9e7f480 100644 --- a/internal/dbs/cockroach/k8s/secrets_test.go +++ b/internal/dbs/postgres/k8s/secrets/secrets_test.go @@ -1,4 +1,4 @@ -package k8s +package secrets import ( "encoding/base64" @@ -22,8 +22,8 @@ func decode(t *testing.T, data interface{}) string { } func TestCockroachSecretFromUnstructured(t *testing.T) { - secret := &CockroachSecret{ - CockroachSecretComparable: CockroachSecretComparable{ + secret := &Resource{ + Comparable: Comparable{ Name: "test-name", Namespace: "test-namespace", DB: DBRef{ diff --git a/internal/dbs/postgres/k8s/services/services.go b/internal/dbs/postgres/k8s/services/services.go new file mode 100644 index 0000000..2b60269 --- /dev/null +++ b/internal/dbs/postgres/k8s/services/services.go @@ -0,0 +1,99 @@ +package services + +import ( + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "services", + }, + Kind: "Service", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "postgrescluster", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + statefulset := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + "labels": k8s_generic.Merge(map[string]string{ + "app": r.Name, + "ponglehub.co.uk/resource-type": "postgrescluster", + }, common.LABEL_FILTERS), + }, + "spec": map[string]interface{}{ + "ports": []map[string]interface{}{ + { + "name": "grpc", + "port": 26257, + "protocol": "TCP", + "targetPort": "grpc", + }, + }, + "selector": map[string]interface{}{ + "app": r.Name, + }, + }, + }, + } + + return statefulset +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + cockroachService, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == cockroachService.Comparable +} diff --git a/internal/dbs/cockroach/k8s/stateful_sets.go b/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go similarity index 64% rename from internal/dbs/cockroach/k8s/stateful_sets.go rename to internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go index 62f5bb4..e6f5772 100644 --- a/internal/dbs/cockroach/k8s/stateful_sets.go +++ b/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go @@ -1,4 +1,4 @@ -package k8s +package stateful_sets import ( "fmt" @@ -9,42 +9,55 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -type CockroachStatefulSetComparable struct { +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "statefulsets", + }, + Kind: "StatefulSet", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "postgrescluster", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { Name string Namespace string Storage string Ready bool } -type CockroachStatefulSet struct { - CockroachStatefulSetComparable +type Resource struct { + Comparable UID string ResourceVersion string } -func (s CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { +func (r Resource) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "StatefulSet", "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, + "name": r.Name, + "namespace": r.Namespace, "labels": k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "cockroachdb", + "ponglehub.co.uk/resource-type": "postgrescluster", }, common.LABEL_FILTERS), }, "spec": map[string]interface{}{ "replicas": 1, "selector": map[string]interface{}{ "matchLabels": map[string]interface{}{ - "app": s.Name, + "app": r.Name, }, }, "template": map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "app": s.Name, + "app": r.Name, }, }, @@ -115,7 +128,7 @@ func (s CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { "metadata": map[string]interface{}{ "name": "datadir", "labels": k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "cockroachdb", + "ponglehub.co.uk/resource-type": "postgrescluster", }, common.LABEL_FILTERS), }, "spec": map[string]interface{}{ @@ -124,7 +137,7 @@ func (s CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { }, "resources": map[string]interface{}{ "requests": map[string]interface{}{ - "storage": s.Storage, + "storage": r.Storage, }, }, }, @@ -137,18 +150,18 @@ func (s CockroachStatefulSet) ToUnstructured() *unstructured.Unstructured { return statefulset } -func cockroachStatefulSetFromUnstructured(obj *unstructured.Unstructured) (CockroachStatefulSet, error) { +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { var err error - s := CockroachStatefulSet{} + r := Resource{} - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() - s.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "volumeClaimTemplates", "0", "spec", "resources", "requests", "storage") + r.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "volumeClaimTemplates", "0", "spec", "resources", "requests", "storage") if err != nil { - return s, fmt.Errorf("failed to get storage: %+v", err) + return r, fmt.Errorf("failed to get storage: %+v", err) } replicas, err := k8s_generic.GetProperty[int64](obj, "status", "replicas") @@ -161,55 +174,39 @@ func cockroachStatefulSetFromUnstructured(obj *unstructured.Unstructured) (Cockr readyReplicas = 0 } - s.Ready = replicas > 0 && replicas == readyReplicas + r.Ready = replicas > 0 && replicas == readyReplicas - return s, nil + return r, nil } -func (s CockroachStatefulSet) GetName() string { - return s.Name +func (r Resource) GetName() string { + return r.Name } -func (s CockroachStatefulSet) GetNamespace() string { - return s.Namespace +func (r Resource) GetNamespace() string { + return r.Namespace } -func (s CockroachStatefulSet) GetUID() string { - return s.UID +func (r Resource) GetUID() string { + return r.UID } -func (s CockroachStatefulSet) GetResourceVersion() string { - return s.ResourceVersion +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion } -func (s CockroachStatefulSet) GetStorage() string { - return s.Storage +func (r Resource) GetStorage() string { + return r.Storage } -func (s CockroachStatefulSet) IsReady() bool { - return s.Ready +func (r Resource) IsReady() bool { + return r.Ready } -func (s CockroachStatefulSet) Equal(obj k8s_generic.Resource) bool { - other, ok := obj.(CockroachStatefulSet) +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(Resource) if !ok { return false } - return s.CockroachStatefulSetComparable == other.CockroachStatefulSetComparable -} - -func (c *Client) StatefulSets() *k8s_generic.Client[CockroachStatefulSet] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "apps", - Version: "v1", - Resource: "statefulsets", - }, - "StatefulSet", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "cockroachdb", - }, common.LABEL_FILTERS), - cockroachStatefulSetFromUnstructured, - ) + return r.Comparable == other.Comparable } diff --git a/internal/dbs/cockroach/managers/database/consolidator.go b/internal/dbs/postgres/managers/database/consolidator.go similarity index 100% rename from internal/dbs/cockroach/managers/database/consolidator.go rename to internal/dbs/postgres/managers/database/consolidator.go diff --git a/internal/dbs/cockroach/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go similarity index 94% rename from internal/dbs/cockroach/managers/database/manager.go rename to internal/dbs/postgres/managers/database/manager.go index d774188..89418f6 100644 --- a/internal/dbs/cockroach/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -5,8 +5,11 @@ import ( "fmt" "time" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/database" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/internal/utils" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -39,7 +42,6 @@ func New( client.Clients().Watch, client.StatefulSets().Watch, client.Secrets().Watch, - client.Migrations().Watch, } { err := f(ctx, cancel, updates) if err != nil { @@ -48,10 +50,9 @@ func New( } state := State{ - clients: bucket.NewBucket[k8s.CockroachClient](), - statefulSets: bucket.NewBucket[k8s.CockroachStatefulSet](), - secrets: bucket.NewBucket[k8s.CockroachSecret](), - migrations: bucket.NewBucket[k8s.CockroachMigration](), + clients: bucket.NewBucket[clients.Resource](), + statefulSets: bucket.NewBucket[stateful_sets.Resource](), + secrets: bucket.NewBucket[secrets.Resource](), databases: bucket.NewBucket[database.Database](), users: bucket.NewBucket[database.User](), permissions: bucket.NewBucket[database.Permission](), diff --git a/internal/dbs/cockroach/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go similarity index 96% rename from internal/dbs/cockroach/managers/database/state.go rename to internal/dbs/postgres/managers/database/state.go index 316f7d1..bf147ec 100644 --- a/internal/dbs/cockroach/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -1,8 +1,8 @@ package database import ( - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/database" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s" "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" diff --git a/internal/dbs/cockroach/managers/deployment/manager.go b/internal/dbs/postgres/managers/deployment/manager.go similarity index 100% rename from internal/dbs/cockroach/managers/deployment/manager.go rename to internal/dbs/postgres/managers/deployment/manager.go diff --git a/internal/dbs/cockroach/managers/deployment/state.go b/internal/dbs/postgres/managers/deployment/state.go similarity index 100% rename from internal/dbs/cockroach/managers/deployment/state.go rename to internal/dbs/postgres/managers/deployment/state.go diff --git a/internal/dbs/redis/k8s/client.go b/internal/dbs/redis/k8s/client.go index d0f322c..2e641cc 100644 --- a/internal/dbs/redis/k8s/client.go +++ b/internal/dbs/redis/k8s/client.go @@ -1,20 +1,11 @@ package k8s import ( - "context" "fmt" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" ) -type K8sClient[T any] interface { - Watch(ctx context.Context, cancel context.CancelFunc) (<-chan k8s_generic.Update[T], error) - Create(ctx context.Context, resource T) error - Delete(ctx context.Context, name string, namespace string) error - Update(ctx context.Context, resource T) error - Event(ctx context.Context, obj T, eventtype, reason, message string) -} - type Client struct { builder *k8s_generic.Builder } diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index fa7946a..bef7aea 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -4,24 +4,24 @@ import ( "fmt" "time" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/managers/database" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/managers/deployment" nats "github.com/benjamin-wright/db-operator/internal/dbs/nats/manager" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/managers/database" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/managers/deployment" redis "github.com/benjamin-wright/db-operator/internal/dbs/redis/manager" ) func Run() (func(), error) { - cockroachDeployManager, err := deployment.New(5 * time.Second) + postgresDeployManager, err := deployment.New(5 * time.Second) if err != nil { - return nil, fmt.Errorf("failed to create cockroach deployment manager: %+v", err) + return nil, fmt.Errorf("failed to create postgres deployment manager: %+v", err) } - cockroachDeployManager.Start() + postgresDeployManager.Start() - cockroachDBManager, err := database.New(5 * time.Second) + postgresDBManager, err := database.New(5 * time.Second) if err != nil { - return nil, fmt.Errorf("failed to create cockroach database manager: %+v", err) + return nil, fmt.Errorf("failed to create postgres database manager: %+v", err) } - cockroachDBManager.Start() + postgresDBManager.Start() redisDeployManager, err := redis.New(5 * time.Second) if err != nil { @@ -36,8 +36,8 @@ func Run() (func(), error) { natsDeployManager.Start() return func() { - cockroachDeployManager.Stop() - cockroachDBManager.Stop() + postgresDeployManager.Stop() + postgresDBManager.Stop() redisDeployManager.Stop() natsDeployManager.Stop() }, nil diff --git a/pkg/k8s_generic/client.go b/pkg/k8s_generic/client.go index 4e6123a..f8013a8 100644 --- a/pkg/k8s_generic/client.go +++ b/pkg/k8s_generic/client.go @@ -37,14 +37,21 @@ type Client[T Resource] struct { fromUnstructured FromUnstructured[T] } -func NewClient[T Resource](b *Builder, resourceSchema schema.GroupVersionResource, kind string, labelFilters map[string]string, fromUnstructured FromUnstructured[T]) *Client[T] { +type ClientArgs[T Resource] struct { + Schema schema.GroupVersionResource + Kind string + LabelFilters map[string]string + FromUnstructured FromUnstructured[T] +} + +func NewClient[T Resource](b *Builder, args ClientArgs[T]) *Client[T] { return &Client[T]{ - schema: resourceSchema, + schema: args.Schema, client: b.dynClient, restClient: b.restClient, - labelFilters: labelFilters, - kind: kind, - fromUnstructured: fromUnstructured, + labelFilters: args.LabelFilters, + kind: args.Kind, + fromUnstructured: args.FromUnstructured, } } From d606e2f84057b053f8bb98e75a20f029685045b8 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Fri, 17 May 2024 22:32:42 +0100 Subject: [PATCH 04/22] WIP: need to add passwords for postgres --- deploy/chart/crds/nats-client.crd.yaml | 8 +- ...nats-db.crd.yaml => nats-cluster.crd.yaml} | 12 +- deploy/chart/crds/postgres-client.crd.yaml | 6 +- ...-db.crd.yaml => postgres-cluster.crd.yaml} | 0 deploy/chart/crds/redis-client.crd.yaml | 8 +- deploy/chart/crds/redis-db.crd.yaml | 12 +- deploy/chart/values.yaml | 2 +- deploy/test-chart/values.yaml | 2 +- internal/dbs/nats/k8s/client.go | 37 +++- internal/dbs/nats/k8s/clients.go | 115 ------------ internal/dbs/nats/k8s/clients/clients.go | 116 ++++++++++++ internal/dbs/nats/k8s/clusters/clusters.go | 77 ++++++++ internal/dbs/nats/k8s/db_ref.go | 6 - internal/dbs/nats/k8s/dbs.go | 81 --------- .../nats/k8s/{ => deployments}/deployments.go | 89 +++++----- internal/dbs/nats/k8s/secrets.go | 114 ------------ internal/dbs/nats/k8s/secrets/secrets.go | 116 ++++++++++++ internal/dbs/nats/k8s/services.go | 101 ----------- internal/dbs/nats/k8s/services/services.go | 98 ++++++++++ internal/dbs/nats/manager/manager.go | 21 ++- internal/dbs/nats/manager/state.go | 67 +++---- internal/dbs/postgres/database/client.go | 10 +- internal/dbs/postgres/database/migrations.go | 2 +- internal/dbs/postgres/database/types.go | 39 ++-- internal/dbs/postgres/k8s/client.go | 2 - internal/dbs/postgres/k8s/clients/clients.go | 30 ++-- .../dbs/postgres/k8s/clusters/clusters.go | 4 +- internal/dbs/postgres/k8s/pvcs/pvcs.go | 4 +- internal/dbs/postgres/k8s/pvcs/pvcs_test.go | 4 +- internal/dbs/postgres/k8s/secrets/secrets.go | 25 ++- .../dbs/postgres/k8s/secrets/secrets_test.go | 2 +- .../k8s/stateful_sets/stateful_sets.go | 38 ++-- .../dbs/postgres/managers/database/manager.go | 168 +++++++----------- .../dbs/postgres/managers/database/state.go | 100 +++++------ .../postgres/managers/deployment/manager.go | 43 +++-- .../dbs/postgres/managers/deployment/state.go | 69 +++---- internal/dbs/redis/k8s/client.go | 44 ++++- internal/dbs/redis/k8s/clients.go | 123 ------------- internal/dbs/redis/k8s/clients/clients.go | 124 +++++++++++++ internal/dbs/redis/k8s/clusters/clusters.go | 92 ++++++++++ internal/dbs/redis/k8s/db_ref.go | 6 - internal/dbs/redis/k8s/dbs.go | 96 ---------- internal/dbs/redis/k8s/pvcs.go | 88 --------- internal/dbs/redis/k8s/pvcs/pvcs.go | 85 +++++++++ internal/dbs/redis/k8s/secrets.go | 127 ------------- internal/dbs/redis/k8s/secrets/secrets.go | 129 ++++++++++++++ internal/dbs/redis/k8s/services.go | 102 ----------- internal/dbs/redis/k8s/services/services.go | 99 +++++++++++ .../k8s/{ => stateful_sets}/stateful_sets.go | 99 +++++------ internal/dbs/redis/manager/manager.go | 24 ++- internal/dbs/redis/manager/state.go | 80 +++++---- internal/state/bucket/bucket.go | 24 ++- internal/state/demand.go | 66 +++---- internal/state/types/interfaces.go | 15 +- pkg/postgres/admin.go | 12 +- pkg/postgres/config.go | 35 ++++ pkg/postgres/connect.go | 10 +- tests/nats_test.go | 14 +- tests/{cockroach_test.go => postgres_test.go} | 42 ++--- tests/redis_test.go | 14 +- 60 files changed, 1604 insertions(+), 1574 deletions(-) rename deploy/chart/crds/{nats-db.crd.yaml => nats-cluster.crd.yaml} (83%) rename deploy/chart/crds/{postgres-db.crd.yaml => postgres-cluster.crd.yaml} (100%) delete mode 100644 internal/dbs/nats/k8s/clients.go create mode 100644 internal/dbs/nats/k8s/clients/clients.go create mode 100644 internal/dbs/nats/k8s/clusters/clusters.go delete mode 100644 internal/dbs/nats/k8s/db_ref.go delete mode 100644 internal/dbs/nats/k8s/dbs.go rename internal/dbs/nats/k8s/{ => deployments}/deployments.go (62%) delete mode 100644 internal/dbs/nats/k8s/secrets.go create mode 100644 internal/dbs/nats/k8s/secrets/secrets.go delete mode 100644 internal/dbs/nats/k8s/services.go create mode 100644 internal/dbs/nats/k8s/services/services.go delete mode 100644 internal/dbs/redis/k8s/clients.go create mode 100644 internal/dbs/redis/k8s/clients/clients.go create mode 100644 internal/dbs/redis/k8s/clusters/clusters.go delete mode 100644 internal/dbs/redis/k8s/db_ref.go delete mode 100644 internal/dbs/redis/k8s/dbs.go delete mode 100644 internal/dbs/redis/k8s/pvcs.go create mode 100644 internal/dbs/redis/k8s/pvcs/pvcs.go delete mode 100644 internal/dbs/redis/k8s/secrets.go create mode 100644 internal/dbs/redis/k8s/secrets/secrets.go delete mode 100644 internal/dbs/redis/k8s/services.go create mode 100644 internal/dbs/redis/k8s/services/services.go rename internal/dbs/redis/k8s/{ => stateful_sets}/stateful_sets.go (64%) rename tests/{cockroach_test.go => postgres_test.go} (57%) diff --git a/deploy/chart/crds/nats-client.crd.yaml b/deploy/chart/crds/nats-client.crd.yaml index 69872d9..2df6fa9 100644 --- a/deploy/chart/crds/nats-client.crd.yaml +++ b/deploy/chart/crds/nats-client.crd.yaml @@ -15,7 +15,7 @@ spec: spec: type: object properties: - dbRef: + cluster: type: object properties: name: @@ -25,7 +25,7 @@ spec: required: [ name, namespace ] secret: type: string - required: [ dbRef, secret ] + required: [ cluster, secret ] status: type: object properties: @@ -44,5 +44,5 @@ spec: singular: natsclient kind: NatsClient shortNames: - - nc - - ncs \ No newline at end of file + - ncl + - ncls \ No newline at end of file diff --git a/deploy/chart/crds/nats-db.crd.yaml b/deploy/chart/crds/nats-cluster.crd.yaml similarity index 83% rename from deploy/chart/crds/nats-db.crd.yaml rename to deploy/chart/crds/nats-cluster.crd.yaml index a30f6d9..1e46748 100644 --- a/deploy/chart/crds/nats-db.crd.yaml +++ b/deploy/chart/crds/nats-cluster.crd.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: natsdbs.ponglehub.co.uk + name: natsclusters.ponglehub.co.uk spec: group: ponglehub.co.uk versions: @@ -26,9 +26,9 @@ spec: jsonPath: .status.ready scope: Namespaced names: - plural: natsdbs - singular: natsdb - kind: NatsDB + plural: natsclusters + singular: natscluster + kind: NatsCluster shortNames: - - ndb - - ndbs \ No newline at end of file + - nc + - ncs \ No newline at end of file diff --git a/deploy/chart/crds/postgres-client.crd.yaml b/deploy/chart/crds/postgres-client.crd.yaml index d83a932..f36ef07 100644 --- a/deploy/chart/crds/postgres-client.crd.yaml +++ b/deploy/chart/crds/postgres-client.crd.yaml @@ -15,7 +15,7 @@ spec: spec: type: object properties: - dbRef: + cluster: type: object properties: name: @@ -23,13 +23,13 @@ spec: namespace: type: string required: [ name, namespace ] - cluster: + database: type: string username: type: string secret: type: string - required: [ dbRef, database, username, secret ] + required: [ cluster, database, username, secret ] status: type: object properties: diff --git a/deploy/chart/crds/postgres-db.crd.yaml b/deploy/chart/crds/postgres-cluster.crd.yaml similarity index 100% rename from deploy/chart/crds/postgres-db.crd.yaml rename to deploy/chart/crds/postgres-cluster.crd.yaml diff --git a/deploy/chart/crds/redis-client.crd.yaml b/deploy/chart/crds/redis-client.crd.yaml index 0707d72..2890fd7 100644 --- a/deploy/chart/crds/redis-client.crd.yaml +++ b/deploy/chart/crds/redis-client.crd.yaml @@ -15,7 +15,7 @@ spec: spec: type: object properties: - dbRef: + cluster: type: object properties: name: @@ -27,7 +27,7 @@ spec: type: integer secret: type: string - required: [ dbRef, unit, secret ] + required: [ cluster, unit, secret ] status: type: object properties: @@ -46,5 +46,5 @@ spec: singular: redisclient kind: RedisClient shortNames: - - rc - - rcs \ No newline at end of file + - rcl + - rcls \ No newline at end of file diff --git a/deploy/chart/crds/redis-db.crd.yaml b/deploy/chart/crds/redis-db.crd.yaml index bba3fd6..e5a4f7c 100644 --- a/deploy/chart/crds/redis-db.crd.yaml +++ b/deploy/chart/crds/redis-db.crd.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: redisdbs.ponglehub.co.uk + name: redisclusters.ponglehub.co.uk spec: group: ponglehub.co.uk versions: @@ -31,9 +31,9 @@ spec: jsonPath: .status.ready scope: Namespaced names: - plural: redisdbs - singular: redisdb - kind: RedisDB + plural: redisclusters + singular: rediscluster + kind: RedisCluster shortNames: - - rdb - - rdbs \ No newline at end of file + - rc + - rcs \ No newline at end of file diff --git a/deploy/chart/values.yaml b/deploy/chart/values.yaml index 30ac57f..d1ad186 100644 --- a/deploy/chart/values.yaml +++ b/deploy/chart/values.yaml @@ -11,5 +11,5 @@ rbacs: resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "delete"] - apiGroups: ["ponglehub.co.uk"] - resources: ["cockroachdbs", "cockroachmigrations", "cockroachclients", "redisdbs", "redisclients", "natsdbs", "natsclients"] + resources: ["postgresclusters", "postgresmigrations", "postgresclients", "redisclusters", "redisclients", "natsclusters", "natsclients"] verbs: ["get", "list", "watch", "update"] \ No newline at end of file diff --git a/deploy/test-chart/values.yaml b/deploy/test-chart/values.yaml index 5edf558..520a755 100644 --- a/deploy/test-chart/values.yaml +++ b/deploy/test-chart/values.yaml @@ -9,5 +9,5 @@ rbacs: resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "delete"] - apiGroups: ["ponglehub.co.uk"] - resources: ["cockroachdbs", "cockroachmigrations", "cockroachclients", "redisdbs", "redisclients", "natsdbs", "natsclients"] + resources: ["postgresclusters", "postgresmigrations", "postgresclients", "redisclusters", "redisclients", "natsclusters", "natsclients"] verbs: ["get", "create", "list", "watch", "update", "deletecollection"] \ No newline at end of file diff --git a/internal/dbs/nats/k8s/client.go b/internal/dbs/nats/k8s/client.go index 2e641cc..5908f43 100644 --- a/internal/dbs/nats/k8s/client.go +++ b/internal/dbs/nats/k8s/client.go @@ -3,11 +3,20 @@ package k8s import ( "fmt" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/deployments" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/services" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" ) type Client struct { - builder *k8s_generic.Builder + clients *k8s_generic.Client[clients.Resource] + clusters *k8s_generic.Client[clusters.Resource] + secrets *k8s_generic.Client[secrets.Resource] + deployments *k8s_generic.Client[deployments.Resource] + services *k8s_generic.Client[services.Resource] } func New() (*Client, error) { @@ -17,6 +26,30 @@ func New() (*Client, error) { } return &Client{ - builder: builder, + clients: k8s_generic.NewClient(builder, clients.ClientArgs), + clusters: k8s_generic.NewClient(builder, clusters.ClientArgs), + secrets: k8s_generic.NewClient(builder, secrets.ClientArgs), + deployments: k8s_generic.NewClient(builder, deployments.ClientArgs), + services: k8s_generic.NewClient(builder, services.ClientArgs), }, nil } + +func (c *Client) Clients() *k8s_generic.Client[clients.Resource] { + return c.clients +} + +func (c *Client) Clusters() *k8s_generic.Client[clusters.Resource] { + return c.clusters +} + +func (c *Client) Secrets() *k8s_generic.Client[secrets.Resource] { + return c.secrets +} + +func (c *Client) Deployments() *k8s_generic.Client[deployments.Resource] { + return c.deployments +} + +func (c *Client) Services() *k8s_generic.Client[services.Resource] { + return c.services +} diff --git a/internal/dbs/nats/k8s/clients.go b/internal/dbs/nats/k8s/clients.go deleted file mode 100644 index 304068c..0000000 --- a/internal/dbs/nats/k8s/clients.go +++ /dev/null @@ -1,115 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type NatsClientComparable struct { - Name string - Namespace string - DBRef DBRef - Secret string -} - -type NatsClient struct { - NatsClientComparable - UID string - ResourceVersion string -} - -func (cli NatsClient) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "NatsClient", - "metadata": map[string]interface{}{ - "name": cli.Name, - "namespace": cli.Namespace, - }, - "spec": map[string]interface{}{ - "dbRef": map[string]interface{}{ - "name": cli.DBRef.Name, - "namespace": cli.DBRef.Namespace, - }, - "secret": cli.Secret, - }, - }) - - return result -} - -func natsClientFromUnstructured(obj *unstructured.Unstructured) (NatsClient, error) { - var err error - cli := NatsClient{} - - cli.Name = obj.GetName() - cli.Namespace = obj.GetNamespace() - cli.UID = string(obj.GetUID()) - cli.ResourceVersion = obj.GetResourceVersion() - - cli.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") - if err != nil { - return cli, fmt.Errorf("failed to get db ref name: %+v", err) - } - - cli.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") - if err != nil { - return cli, fmt.Errorf("failed to get db ref namespace: %+v", err) - } - - cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") - if err != nil { - return cli, fmt.Errorf("failed to get secret: %+v", err) - } - - return cli, nil -} - -func (cli NatsClient) GetName() string { - return cli.Name -} - -func (cli NatsClient) GetNamespace() string { - return cli.Namespace -} - -func (cli NatsClient) GetUID() string { - return cli.UID -} - -func (cli NatsClient) GetResourceVersion() string { - return cli.ResourceVersion -} - -func (cli NatsClient) GetTarget() string { - return cli.DBRef.Name -} - -func (cli NatsClient) GetTargetNamespace() string { - return cli.DBRef.Namespace -} - -func (cli NatsClient) Equal(obj k8s_generic.Resource) bool { - if other, ok := obj.(NatsClient); ok { - return cli.NatsClientComparable == other.NatsClientComparable - } - return false -} - -func (c *Client) Clients() *k8s_generic.Client[NatsClient] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "natsclients", - }, - "NatsClient", - nil, - natsClientFromUnstructured, - ) -} diff --git a/internal/dbs/nats/k8s/clients/clients.go b/internal/dbs/nats/k8s/clients/clients.go new file mode 100644 index 0000000..59d583d --- /dev/null +++ b/internal/dbs/nats/k8s/clients/clients.go @@ -0,0 +1,116 @@ +package clients + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "ponglehub.co.uk", + Version: "v1alpha1", + Resource: "natsclients", + }, + Kind: "Resource", + FromUnstructured: fromUnstructured, +} + +type Cluster struct { + Name string + Namespace string +} + +type Comparable struct { + Name string + Namespace string + Cluster Cluster + Secret string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (cli Resource) ToUnstructured() *unstructured.Unstructured { + result := &unstructured.Unstructured{} + result.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "ponglehub.co.uk/v1alpha1", + "kind": "NatsClient", + "metadata": map[string]interface{}{ + "name": cli.Name, + "namespace": cli.Namespace, + }, + "spec": map[string]interface{}{ + "cluster": map[string]interface{}{ + "name": cli.Cluster.Name, + "namespace": cli.Cluster.Namespace, + }, + "secret": cli.Secret, + }, + }) + + return result +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + var err error + cli := Resource{} + + cli.Name = obj.GetName() + cli.Namespace = obj.GetNamespace() + cli.UID = string(obj.GetUID()) + cli.ResourceVersion = obj.GetResourceVersion() + + cli.Cluster.Name, err = k8s_generic.GetProperty[string](obj, "spec", "cluster", "name") + if err != nil { + return cli, fmt.Errorf("failed to get cluster name: %+v", err) + } + + cli.Cluster.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "cluster", "namespace") + if err != nil { + return cli, fmt.Errorf("failed to get cluster namespace: %+v", err) + } + + cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") + if err != nil { + return cli, fmt.Errorf("failed to get secret: %+v", err) + } + + return cli, nil +} + +func (cli Resource) GetName() string { + return cli.Name +} + +func (cli Resource) GetNamespace() string { + return cli.Namespace +} + +func (cli Resource) GetUID() string { + return cli.UID +} + +func (cli Resource) GetResourceVersion() string { + return cli.ResourceVersion +} + +func (cli Resource) GetTarget() string { + return cli.Cluster.Name +} + +func (cli Resource) GetTargetNamespace() string { + return cli.Cluster.Namespace +} + +func (cli Resource) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(Resource); ok { + return cli.Comparable == other.Comparable + } + return false +} diff --git a/internal/dbs/nats/k8s/clusters/clusters.go b/internal/dbs/nats/k8s/clusters/clusters.go new file mode 100644 index 0000000..10fd255 --- /dev/null +++ b/internal/dbs/nats/k8s/clusters/clusters.go @@ -0,0 +1,77 @@ +package clusters + +import ( + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "ponglehub.co.uk", + Version: "v1alpha1", + Resource: "natsclusters", + }, + Kind: "NatsCluster", + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + result := &unstructured.Unstructured{} + result.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "ponglehub.co.uk/v1alpha1", + "kind": "NatsCluster", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + }, + }) + + return result +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/nats/k8s/db_ref.go b/internal/dbs/nats/k8s/db_ref.go deleted file mode 100644 index 396a672..0000000 --- a/internal/dbs/nats/k8s/db_ref.go +++ /dev/null @@ -1,6 +0,0 @@ -package k8s - -type DBRef struct { - Name string - Namespace string -} diff --git a/internal/dbs/nats/k8s/dbs.go b/internal/dbs/nats/k8s/dbs.go deleted file mode 100644 index bd42ef3..0000000 --- a/internal/dbs/nats/k8s/dbs.go +++ /dev/null @@ -1,81 +0,0 @@ -package k8s - -import ( - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type NatsDBComparable struct { - Name string - Namespace string -} - -type NatsDB struct { - NatsDBComparable - UID string - ResourceVersion string -} - -func (db NatsDB) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "NatsDB", - "metadata": map[string]interface{}{ - "name": db.Name, - "namespace": db.Namespace, - }, - }) - - return result -} - -func natsDBFromUnstructured(obj *unstructured.Unstructured) (NatsDB, error) { - db := NatsDB{} - - db.Name = obj.GetName() - db.Namespace = obj.GetNamespace() - db.UID = string(obj.GetUID()) - db.ResourceVersion = obj.GetResourceVersion() - - return db, nil -} - -func (db NatsDB) GetName() string { - return db.Name -} - -func (db NatsDB) GetNamespace() string { - return db.Namespace -} - -func (db NatsDB) GetUID() string { - return db.UID -} - -func (db NatsDB) GetResourceVersion() string { - return db.ResourceVersion -} - -func (db NatsDB) Equal(obj k8s_generic.Resource) bool { - natsDB, ok := obj.(*NatsDB) - if !ok { - return false - } - return db.NatsDBComparable == natsDB.NatsDBComparable -} - -func (c *Client) DBs() *k8s_generic.Client[NatsDB] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "natsdbs", - }, - "NatsDB", - nil, - natsDBFromUnstructured, - ) -} diff --git a/internal/dbs/nats/k8s/deployments.go b/internal/dbs/nats/k8s/deployments/deployments.go similarity index 62% rename from internal/dbs/nats/k8s/deployments.go rename to internal/dbs/nats/k8s/deployments/deployments.go index 809d67c..61efa0b 100644 --- a/internal/dbs/nats/k8s/deployments.go +++ b/internal/dbs/nats/k8s/deployments/deployments.go @@ -1,4 +1,4 @@ -package k8s +package deployments import ( "github.com/benjamin-wright/db-operator/internal/common" @@ -7,26 +7,39 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -type NatsDeploymentComparable struct { +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "deployments", + }, + Kind: "Deployment", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "nats", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { Name string Namespace string Ready bool } -type NatsDeployment struct { - NatsDeploymentComparable +type Resource struct { + Comparable UID string ResourceVersion string } -func (d NatsDeployment) ToUnstructured() *unstructured.Unstructured { +func (r Resource) ToUnstructured() *unstructured.Unstructured { deployment := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ - "name": d.Name, - "namespace": d.Namespace, + "name": r.Name, + "namespace": r.Namespace, "labels": k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "nats", }, common.LABEL_FILTERS), @@ -35,13 +48,13 @@ func (d NatsDeployment) ToUnstructured() *unstructured.Unstructured { "replicas": 1, "selector": map[string]interface{}{ "matchLabels": map[string]interface{}{ - "app": d.Name, + "app": r.Name, }, }, "template": map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "app": d.Name, + "app": r.Name, }, }, @@ -96,14 +109,14 @@ func (d NatsDeployment) ToUnstructured() *unstructured.Unstructured { return deployment } -func natsDeploymentFromUnstructured(obj *unstructured.Unstructured) (NatsDeployment, error) { +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { var err error - d := NatsDeployment{} + r := Resource{} - d.Name = obj.GetName() - d.Namespace = obj.GetNamespace() - d.UID = string(obj.GetUID()) - d.ResourceVersion = obj.GetResourceVersion() + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() replicas, err := k8s_generic.GetProperty[int64](obj, "status", "replicas") if err != nil { @@ -115,50 +128,34 @@ func natsDeploymentFromUnstructured(obj *unstructured.Unstructured) (NatsDeploym readyReplicas = 0 } - d.Ready = replicas > 0 && replicas == readyReplicas + r.Ready = replicas > 0 && replicas == readyReplicas - return d, nil + return r, nil } -func (d NatsDeployment) GetName() string { - return d.Name +func (r Resource) GetName() string { + return r.Name } -func (d NatsDeployment) GetNamespace() string { - return d.Namespace +func (r Resource) GetNamespace() string { + return r.Namespace } -func (d NatsDeployment) GetUID() string { - return d.UID +func (r Resource) GetUID() string { + return r.UID } -func (d NatsDeployment) GetResourceVersion() string { - return d.ResourceVersion +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion } -func (d NatsDeployment) IsReady() bool { - return d.Ready +func (r Resource) IsReady() bool { + return r.Ready } -func (d NatsDeployment) Equal(obj k8s_generic.Resource) bool { - if natsDeployment, ok := obj.(*NatsDeployment); ok { - return d.NatsDeploymentComparable == natsDeployment.NatsDeploymentComparable +func (r Resource) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(*Resource); ok { + return r.Comparable == other.Comparable } return false } - -func (c *Client) Deployments() *k8s_generic.Client[NatsDeployment] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "apps", - Version: "v1", - Resource: "deployments", - }, - "Deployment", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "nats", - }, common.LABEL_FILTERS), - natsDeploymentFromUnstructured, - ) -} diff --git a/internal/dbs/nats/k8s/secrets.go b/internal/dbs/nats/k8s/secrets.go deleted file mode 100644 index 1867ffe..0000000 --- a/internal/dbs/nats/k8s/secrets.go +++ /dev/null @@ -1,114 +0,0 @@ -package k8s - -import ( - "encoding/base64" - "fmt" - "strings" - - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type NatsSecretComparable struct { - Name string - Namespace string - DB DBRef -} - -type NatsSecret struct { - NatsSecretComparable - UID string - ResourceVersion string -} - -func (s NatsSecret) GetHost() string { - return fmt.Sprintf("%s.%s.svc.cluster.local", s.DB.Name, s.DB.Namespace) -} - -func encode(data string) string { - return base64.StdEncoding.EncodeToString([]byte(data)) -} - -func (s NatsSecret) ToUnstructured() *unstructured.Unstructured { - secret := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, - "labels": k8s_generic.Merge(map[string]string{ - "app": s.Name, - "ponglehub.co.uk/resource-type": "nats", - }, common.LABEL_FILTERS), - }, - "data": map[string]interface{}{ - "NATS_HOST": encode(s.GetHost()), - "NATS_PORT": encode("4222"), - }, - }, - } - - return secret -} - -func natsSecretFromUnstructured(obj *unstructured.Unstructured) (NatsSecret, error) { - s := NatsSecret{} - - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() - - hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "NATS_HOST") - if err != nil { - return s, fmt.Errorf("failed to get NATS_HOST: %+v", err) - } - s.DB.Name = strings.Split(hostname, ".")[0] - s.DB.Namespace = strings.Split(hostname, ".")[1] - - return s, nil -} - -func (s NatsSecret) GetName() string { - return s.Name -} - -func (s NatsSecret) GetNamespace() string { - return s.Namespace -} - -func (s NatsSecret) GetUID() string { - return s.UID -} - -func (s NatsSecret) GetResourceVersion() string { - return s.ResourceVersion -} - -func (s NatsSecret) Equal(obj k8s_generic.Resource) bool { - other, ok := obj.(NatsSecret) - if !ok { - return false - } - return s.NatsSecretComparable == other.NatsSecretComparable -} - -func (c *Client) Secrets() *k8s_generic.Client[NatsSecret] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "secrets", - }, - "Secret", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "nats", - }, common.LABEL_FILTERS), - natsSecretFromUnstructured, - ) -} diff --git a/internal/dbs/nats/k8s/secrets/secrets.go b/internal/dbs/nats/k8s/secrets/secrets.go new file mode 100644 index 0000000..7363f74 --- /dev/null +++ b/internal/dbs/nats/k8s/secrets/secrets.go @@ -0,0 +1,116 @@ +package secrets + +import ( + "encoding/base64" + "fmt" + "strings" + + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "secrets", + }, + Kind: "Secret", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "nats", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Cluster struct { + Name string + Namespace string +} + +type Comparable struct { + Name string + Namespace string + Cluster Cluster +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) GetHost() string { + return fmt.Sprintf("%s.%s.svc.cluster.local", r.Cluster.Name, r.Cluster.Namespace) +} + +func encode(data string) string { + return base64.StdEncoding.EncodeToString([]byte(data)) +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + "labels": k8s_generic.Merge(map[string]string{ + "app": r.Name, + "ponglehub.co.uk/resource-type": "nats", + }, common.LABEL_FILTERS), + }, + "data": map[string]interface{}{ + "NATS_HOST": encode(r.GetHost()), + "NATS_PORT": encode("4222"), + }, + }, + } + + return secret +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "NATS_HOST") + if err != nil { + return r, fmt.Errorf("failed to get NATS_HOST: %+v", err) + } + r.Cluster.Name = strings.Split(hostname, ".")[0] + r.Cluster.Namespace = strings.Split(hostname, ".")[1] + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/nats/k8s/services.go b/internal/dbs/nats/k8s/services.go deleted file mode 100644 index 45ac628..0000000 --- a/internal/dbs/nats/k8s/services.go +++ /dev/null @@ -1,101 +0,0 @@ -package k8s - -import ( - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type NatsServiceComparable struct { - Name string - Namespace string -} - -type NatsService struct { - NatsServiceComparable - UID string - ResourceVersion string -} - -func (s NatsService) ToUnstructured() *unstructured.Unstructured { - statefulset := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Service", - "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, - "labels": k8s_generic.Merge(map[string]string{ - "app": s.Name, - "ponglehub.co.uk/resource-type": "nats", - }, common.LABEL_FILTERS), - }, - "spec": map[string]interface{}{ - "ports": []map[string]interface{}{ - { - "name": "tcp", - "port": 4222, - "protocol": "TCP", - "targetPort": "tcp", - }, - }, - "selector": map[string]interface{}{ - "app": s.Name, - }, - }, - }, - } - - return statefulset -} - -func natsServiceFromUnstructured(obj *unstructured.Unstructured) (NatsService, error) { - s := NatsService{} - - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() - - return s, nil -} - -func (s NatsService) GetName() string { - return s.Name -} - -func (s NatsService) GetNamespace() string { - return s.Namespace -} - -func (s NatsService) GetUID() string { - return s.UID -} - -func (s NatsService) GetResourceVersion() string { - return s.ResourceVersion -} - -func (s NatsService) Equal(obj k8s_generic.Resource) bool { - if natsService, ok := obj.(*NatsService); ok { - return s.NatsServiceComparable == natsService.NatsServiceComparable - } - return false -} - -func (c *Client) Services() *k8s_generic.Client[NatsService] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "services", - }, - "Service", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "nats", - }, common.LABEL_FILTERS), - natsServiceFromUnstructured, - ) -} diff --git a/internal/dbs/nats/k8s/services/services.go b/internal/dbs/nats/k8s/services/services.go new file mode 100644 index 0000000..bfce302 --- /dev/null +++ b/internal/dbs/nats/k8s/services/services.go @@ -0,0 +1,98 @@ +package services + +import ( + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "services", + }, + Kind: "Service", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "nats", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + statefulset := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + "labels": k8s_generic.Merge(map[string]string{ + "app": r.Name, + "ponglehub.co.uk/resource-type": "nats", + }, common.LABEL_FILTERS), + }, + "spec": map[string]interface{}{ + "ports": []map[string]interface{}{ + { + "name": "tcp", + "port": 4222, + "protocol": "TCP", + "targetPort": "tcp", + }, + }, + "selector": map[string]interface{}{ + "app": r.Name, + }, + }, + }, + } + + return statefulset +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + if other, ok := obj.(*Resource); ok { + return r.Comparable == other.Comparable + } + return false +} diff --git a/internal/dbs/nats/manager/manager.go b/internal/dbs/nats/manager/manager.go index ee7a348..a59c39b 100644 --- a/internal/dbs/nats/manager/manager.go +++ b/internal/dbs/nats/manager/manager.go @@ -6,6 +6,11 @@ import ( "time" "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/deployments" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/services" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/internal/utils" "github.com/rs/zerolog/log" @@ -34,7 +39,7 @@ func New( updates := make(chan any) for _, f := range []WatchFunc{ - client.DBs().Watch, + client.Clusters().Watch, client.Clients().Watch, client.Deployments().Watch, client.Services().Watch, @@ -47,11 +52,11 @@ func New( } state := State{ - dbs: bucket.NewBucket[k8s.NatsDB](), - clients: bucket.NewBucket[k8s.NatsClient](), - statefulSets: bucket.NewBucket[k8s.NatsDeployment](), - services: bucket.NewBucket[k8s.NatsService](), - secrets: bucket.NewBucket[k8s.NatsSecret](), + clusters: bucket.NewBucket[clusters.Resource](), + clients: bucket.NewBucket[clients.Resource](), + deployments: bucket.NewBucket[deployments.Resource](), + services: bucket.NewBucket[services.Resource](), + secrets: bucket.NewBucket[secrets.Resource](), } return &Manager{ @@ -123,9 +128,9 @@ func (m *Manager) processNatsDBs() { err := m.client.Deployments().Create(m.ctx, db.Target) if err != nil { log.Error().Err(err).Msg("Failed to create nats deployment") - m.client.DBs().Event(m.ctx, db.Parent, "Normal", "ProvisioningFailed", fmt.Sprintf("Failed to create deployment: %s", err.Error())) + m.client.Clusters().Event(m.ctx, db.Parent, "Normal", "ProvisioningFailed", fmt.Sprintf("Failed to create deployment: %s", err.Error())) } else { - m.client.DBs().Event(m.ctx, db.Parent, "Normal", "ProvisioningSucceeded", "Created deployment") + m.client.Clusters().Event(m.ctx, db.Parent, "Normal", "ProvisioningSucceeded", "Created deployment") } } diff --git a/internal/dbs/nats/manager/state.go b/internal/dbs/nats/manager/state.go index 23ce276..e37b16b 100644 --- a/internal/dbs/nats/manager/state.go +++ b/internal/dbs/nats/manager/state.go @@ -1,7 +1,11 @@ package manager import ( - "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/deployments" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/services" "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -9,37 +13,37 @@ import ( ) type State struct { - dbs bucket.Bucket[k8s.NatsDB, *k8s.NatsDB] - clients bucket.Bucket[k8s.NatsClient, *k8s.NatsClient] - statefulSets bucket.Bucket[k8s.NatsDeployment, *k8s.NatsDeployment] - services bucket.Bucket[k8s.NatsService, *k8s.NatsService] - secrets bucket.Bucket[k8s.NatsSecret, *k8s.NatsSecret] + clusters bucket.Bucket[clusters.Resource] + clients bucket.Bucket[clients.Resource] + deployments bucket.Bucket[deployments.Resource] + services bucket.Bucket[services.Resource] + secrets bucket.Bucket[secrets.Resource] } func (s *State) Apply(update interface{}) { switch u := update.(type) { - case k8s_generic.Update[k8s.NatsDB]: - s.dbs.Apply(u) - case k8s_generic.Update[k8s.NatsClient]: + case k8s_generic.Update[clusters.Resource]: + s.clusters.Apply(u) + case k8s_generic.Update[clients.Resource]: s.clients.Apply(u) - case k8s_generic.Update[k8s.NatsDeployment]: - s.statefulSets.Apply(u) - case k8s_generic.Update[k8s.NatsService]: + case k8s_generic.Update[deployments.Resource]: + s.deployments.Apply(u) + case k8s_generic.Update[services.Resource]: s.services.Apply(u) - case k8s_generic.Update[k8s.NatsSecret]: + case k8s_generic.Update[secrets.Resource]: s.secrets.Apply(u) default: log.Error().Interface("update", u).Msg("wat dis? Unknown state update") } } -func (s *State) GetDeploymentDemand() state.Demand[k8s.NatsDB, k8s.NatsDeployment] { +func (s *State) GetDeploymentDemand() state.Demand[clusters.Resource, deployments.Resource] { return state.GetOneForOne( - s.dbs, - s.statefulSets, - func(db k8s.NatsDB) k8s.NatsDeployment { - return k8s.NatsDeployment{ - NatsDeploymentComparable: k8s.NatsDeploymentComparable{ + s.clusters, + s.deployments, + func(db clusters.Resource) deployments.Resource { + return deployments.Resource{ + Comparable: deployments.Comparable{ Name: db.Name, Namespace: db.Namespace, }, @@ -48,13 +52,13 @@ func (s *State) GetDeploymentDemand() state.Demand[k8s.NatsDB, k8s.NatsDeploymen ) } -func (s *State) GetServiceDemand() state.Demand[k8s.NatsDB, k8s.NatsService] { +func (s *State) GetServiceDemand() state.Demand[clusters.Resource, services.Resource] { return state.GetOneForOne( - s.dbs, + s.clusters, s.services, - func(db k8s.NatsDB) k8s.NatsService { - return k8s.NatsService{ - NatsServiceComparable: k8s.NatsServiceComparable{ + func(db clusters.Resource) services.Resource { + return services.Resource{ + Comparable: services.Comparable{ Name: db.Name, Namespace: db.Namespace, }, @@ -63,17 +67,20 @@ func (s *State) GetServiceDemand() state.Demand[k8s.NatsDB, k8s.NatsService] { ) } -func (s *State) GetSecretsDemand() state.Demand[k8s.NatsClient, k8s.NatsSecret] { +func (s *State) GetSecretsDemand() state.Demand[clients.Resource, secrets.Resource] { return state.GetServiceBound( s.clients, s.secrets, - s.statefulSets, - func(client k8s.NatsClient) k8s.NatsSecret { - return k8s.NatsSecret{ - NatsSecretComparable: k8s.NatsSecretComparable{ + s.deployments, + func(client clients.Resource) secrets.Resource { + return secrets.Resource{ + Comparable: secrets.Comparable{ Name: client.Secret, Namespace: client.Namespace, - DB: client.DBRef, + Cluster: secrets.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, + }, }, } }, diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index 8034c96..32d5888 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -17,7 +17,7 @@ func New(database string, namespace string) (*Client, error) { cfg := postgres.ConnectConfig{ Host: fmt.Sprintf("%s.%s.svc.cluster.local", database, namespace), Port: 26257, - Username: "root", + Username: "postgres", } conn, err := postgres.NewAdminConn(cfg) @@ -54,7 +54,7 @@ func (c *Client) ListDBs() ([]Database, error) { } databases = append(databases, Database{ - DB: DBRef{ + Cluster: Cluster{ Name: c.database, Namespace: c.namespace, }, @@ -100,7 +100,7 @@ func (c *Client) ListUsers() ([]User, error) { } users = append(users, User{ - DB: DBRef{ + Cluster: Cluster{ Name: c.database, Namespace: c.namespace, }, @@ -112,7 +112,7 @@ func (c *Client) ListUsers() ([]User, error) { } func (c *Client) CreateUser(user User) error { - err := c.conn.CreateUser(user.Name) + err := c.conn.CreateUser(user.Name, user.Password) if err != nil { return fmt.Errorf("failed to create user %s: %+v", user, err) } @@ -142,7 +142,7 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { } permissions = append(permissions, Permission{ - DB: DBRef{ + Cluster: Cluster{ Name: c.database, Namespace: c.namespace, }, diff --git a/internal/dbs/postgres/database/migrations.go b/internal/dbs/postgres/database/migrations.go index 1333c45..3913c7e 100644 --- a/internal/dbs/postgres/database/migrations.go +++ b/internal/dbs/postgres/database/migrations.go @@ -82,7 +82,7 @@ func (c *MigrationsClient) AppliedMigrations() ([]Migration, error) { } migrations = append(migrations, Migration{ - DB: DBRef{ + Cluster: Cluster{ Name: c.deployment, Namespace: c.namespace, }, diff --git a/internal/dbs/postgres/database/types.go b/internal/dbs/postgres/database/types.go index 81fb005..8970624 100644 --- a/internal/dbs/postgres/database/types.go +++ b/internal/dbs/postgres/database/types.go @@ -2,61 +2,62 @@ package database import "strconv" -type DBRef struct { +type Cluster struct { Name string Namespace string } type Database struct { - Name string - DB DBRef + Name string + Cluster Cluster } -func (d *Database) GetName() string { +func (d Database) GetName() string { return d.Name } -func (d *Database) GetNamespace() string { - return d.DB.Namespace + ":" + d.DB.Name +func (d Database) GetNamespace() string { + return d.Cluster.Namespace + ":" + d.Cluster.Name } type User struct { - Name string - DB DBRef + Name string + Password string + Cluster Cluster } -func (u *User) GetName() string { +func (u User) GetName() string { return u.Name } -func (u *User) GetNamespace() string { - return u.DB.Namespace + ":" + u.DB.Name +func (u User) GetNamespace() string { + return u.Cluster.Namespace + ":" + u.Cluster.Name } type Permission struct { User string Database string - DB DBRef + Cluster Cluster } -func (u *Permission) GetName() string { +func (u Permission) GetName() string { return u.Database + u.User } -func (u *Permission) GetNamespace() string { - return u.DB.Namespace + ":" + u.DB.Name +func (u Permission) GetNamespace() string { + return u.Cluster.Namespace + ":" + u.Cluster.Name } type Migration struct { - DB DBRef + Cluster Cluster Database string Index int64 } -func (m *Migration) GetName() string { +func (m Migration) GetName() string { return m.Database + strconv.FormatInt(m.Index, 10) } -func (m *Migration) GetNamespace() string { - return m.DB.Namespace + ":" + m.DB.Name +func (m Migration) GetNamespace() string { + return m.Cluster.Namespace + ":" + m.Cluster.Name } diff --git a/internal/dbs/postgres/k8s/client.go b/internal/dbs/postgres/k8s/client.go index 5bba343..cc5048f 100644 --- a/internal/dbs/postgres/k8s/client.go +++ b/internal/dbs/postgres/k8s/client.go @@ -13,7 +13,6 @@ import ( ) type Client struct { - builder *k8s_generic.Builder clients *k8s_generic.Client[clients.Resource] clusters *k8s_generic.Client[clusters.Resource] pvcs *k8s_generic.Client[pvcs.Resource] @@ -29,7 +28,6 @@ func New() (*Client, error) { } return &Client{ - builder: builder, clients: k8s_generic.NewClient(builder, clients.ClientArgs), clusters: k8s_generic.NewClient(builder, clusters.ClientArgs), pvcs: k8s_generic.NewClient(builder, pvcs.ClientArgs), diff --git a/internal/dbs/postgres/k8s/clients/clients.go b/internal/dbs/postgres/k8s/clients/clients.go index 3ec8804..4faa139 100644 --- a/internal/dbs/postgres/k8s/clients/clients.go +++ b/internal/dbs/postgres/k8s/clients/clients.go @@ -18,7 +18,7 @@ var ClientArgs = k8s_generic.ClientArgs[Resource]{ FromUnstructured: fromUnstructured, } -type DBRef struct { +type Cluster struct { Name string Namespace string } @@ -26,8 +26,8 @@ type DBRef struct { type Comparable struct { Name string Namespace string - DBRef DBRef - Cluster string + Cluster Cluster + Database string Username string Secret string } @@ -48,11 +48,11 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { "namespace": r.Namespace, }, "spec": map[string]interface{}{ - "dbRef": map[string]interface{}{ - "name": r.DBRef.Name, - "namespace": r.DBRef.Namespace, + "cluster": map[string]interface{}{ + "name": r.Cluster.Name, + "namespace": r.Cluster.Namespace, }, - "cluster": r.Cluster, + "database": r.Database, "username": r.Username, "secret": r.Secret, }, @@ -69,19 +69,19 @@ func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { r.UID = string(obj.GetUID()) r.ResourceVersion = obj.GetResourceVersion() - r.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") + r.Cluster.Name, err = k8s_generic.GetProperty[string](obj, "spec", "cluster", "name") if err != nil { - return r, fmt.Errorf("failed to get db ref name: %+v", err) + return r, fmt.Errorf("failed to get cluster ref name: %+v", err) } - r.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") + r.Cluster.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "cluster", "namespace") if err != nil { - return r, fmt.Errorf("failed to get db ref namespace: %+v", err) + return r, fmt.Errorf("failed to get cluster ref namespace: %+v", err) } - r.Cluster, err = k8s_generic.GetProperty[string](obj, "spec", "cluster") + r.Database, err = k8s_generic.GetProperty[string](obj, "spec", "database") if err != nil { - return r, fmt.Errorf("failed to get cluster: %+v", err) + return r, fmt.Errorf("failed to get database: %+v", err) } r.Username, err = k8s_generic.GetProperty[string](obj, "spec", "username") @@ -114,11 +114,11 @@ func (r Resource) GetResourceVersion() string { } func (r Resource) GetTarget() string { - return r.DBRef.Name + return r.Cluster.Name } func (r Resource) GetTargetNamespace() string { - return r.DBRef.Namespace + return r.Cluster.Namespace } func (r Resource) Equal(obj k8s_generic.Resource) bool { diff --git a/internal/dbs/postgres/k8s/clusters/clusters.go b/internal/dbs/postgres/k8s/clusters/clusters.go index baed853..78656a0 100644 --- a/internal/dbs/postgres/k8s/clusters/clusters.go +++ b/internal/dbs/postgres/k8s/clusters/clusters.go @@ -14,7 +14,7 @@ var ClientArgs = k8s_generic.ClientArgs[Resource]{ Version: "v1alpha1", Resource: "postgresclusters", }, - Kind: "Cluster", + Kind: "PostgresCluster", FromUnstructured: fromUnstructured, } @@ -34,7 +34,7 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { result := &unstructured.Unstructured{} result.SetUnstructuredContent(map[string]interface{}{ "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "Cluster", + "kind": "PostgresCluster", "metadata": map[string]interface{}{ "name": r.Name, "namespace": r.Namespace, diff --git a/internal/dbs/postgres/k8s/pvcs/pvcs.go b/internal/dbs/postgres/k8s/pvcs/pvcs.go index 68a8341..fa9146f 100644 --- a/internal/dbs/postgres/k8s/pvcs/pvcs.go +++ b/internal/dbs/postgres/k8s/pvcs/pvcs.go @@ -25,7 +25,7 @@ var ClientArgs = k8s_generic.ClientArgs[Resource]{ type Comparable struct { Name string Namespace string - Database string + Cluster string Storage string } @@ -53,7 +53,7 @@ func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { return r, fmt.Errorf("failed to get storage: %+v", err) } - r.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") + r.Cluster, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") if err != nil { return r, fmt.Errorf("failed to get database from app label: %+v", err) } diff --git a/internal/dbs/postgres/k8s/pvcs/pvcs_test.go b/internal/dbs/postgres/k8s/pvcs/pvcs_test.go index 20571de..33caa15 100644 --- a/internal/dbs/postgres/k8s/pvcs/pvcs_test.go +++ b/internal/dbs/postgres/k8s/pvcs/pvcs_test.go @@ -17,7 +17,7 @@ func TestCockroachPVCFromUnstructured(t *testing.T) { "resourceVersion": "test-resource-version", "creationTimestamp": "test-creation-timestamp", "labels": map[string]interface{}{ - "app": "test-database", + "app": "test-cluster", }, }, "spec": map[string]interface{}{ @@ -38,5 +38,5 @@ func TestCockroachPVCFromUnstructured(t *testing.T) { assert.Equal(t, "test-uid", cockroachPVC.UID) assert.Equal(t, "test-resource-version", cockroachPVC.ResourceVersion) assert.Equal(t, "test-storage", cockroachPVC.Storage) - assert.Equal(t, "test-database", cockroachPVC.Database) + assert.Equal(t, "test-cluster", cockroachPVC.Cluster) } diff --git a/internal/dbs/postgres/k8s/secrets/secrets.go b/internal/dbs/postgres/k8s/secrets/secrets.go index 9c689e2..cae187e 100644 --- a/internal/dbs/postgres/k8s/secrets/secrets.go +++ b/internal/dbs/postgres/k8s/secrets/secrets.go @@ -24,7 +24,7 @@ var ClientArgs = k8s_generic.ClientArgs[Resource]{ FromUnstructured: fromUnstructured, } -type DBRef struct { +type Cluster struct { Name string Namespace string } @@ -32,9 +32,10 @@ type DBRef struct { type Comparable struct { Name string Namespace string - DB DBRef + Cluster Cluster Database string User string + Password string } type Resource struct { @@ -48,7 +49,7 @@ func encode(data string) string { } func (r Resource) GetHost() string { - return fmt.Sprintf("%s.%s.svc.cluster.local", r.DB.Name, r.DB.Namespace) + return fmt.Sprintf("%s.%s.svc.cluster.local", r.Cluster.Name, r.Cluster.Namespace) } func (r Resource) GetPort() string { @@ -69,10 +70,11 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { "namespace": r.Namespace, }, "data": map[string]interface{}{ - "POSTGRES_HOST": encode(r.GetHost()), - "POSTGRES_PORT": encode(r.GetPort()), - "POSTGRES_USER": encode(r.User), - "POSTGRES_NAME": encode(r.Database), + "POSTGRES_HOST": encode(r.GetHost()), + "POSTGRES_PORT": encode(r.GetPort()), + "POSTGRES_USER": encode(r.User), + "POSTGRES_PASSWORD": encode(r.Password), + "POSTGRES_NAME": encode(r.Database), }, }, } @@ -93,14 +95,19 @@ func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { if err != nil { return r, fmt.Errorf("failed to get POSTGRES_HOST: %+v", err) } - r.DB.Name = strings.Split(hostname, ".")[0] - r.DB.Namespace = strings.Split(hostname, ".")[1] + r.Cluster.Name = strings.Split(hostname, ".")[0] + r.Cluster.Namespace = strings.Split(hostname, ".")[1] r.User, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_USER") if err != nil { return r, fmt.Errorf("failed to get POSTGRES_USER: %+v", err) } + r.Password, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_PASSWORD") + if err != nil { + return r, fmt.Errorf("failed to get POSTGRES_PASSWORD: %+v", err) + } + r.Database, err = k8s_generic.GetEncodedProperty(obj, "data", "POSTGRES_NAME") if err != nil { return r, fmt.Errorf("failed to get POSTGRES_NAME: %+v", err) diff --git a/internal/dbs/postgres/k8s/secrets/secrets_test.go b/internal/dbs/postgres/k8s/secrets/secrets_test.go index 9e7f480..f4e1919 100644 --- a/internal/dbs/postgres/k8s/secrets/secrets_test.go +++ b/internal/dbs/postgres/k8s/secrets/secrets_test.go @@ -26,7 +26,7 @@ func TestCockroachSecretFromUnstructured(t *testing.T) { Comparable: Comparable{ Name: "test-name", Namespace: "test-namespace", - DB: DBRef{ + Cluster: Cluster{ Name: "test-db", Namespace: "db-namespace", }, diff --git a/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go b/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go index e6f5772..09b91e0 100644 --- a/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go +++ b/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go @@ -65,14 +65,16 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { "containers": []map[string]interface{}{ { "name": "database", - "image": "cockroachdb/cockroach:v22.2.8", - "command": []string{ - "cockroach", - }, - "args": []string{ - "--logtostderr", - "start-single-node", - "--insecure", + "image": "postgres:16.3", + "env": []map[string]interface{}{ + { + "name": "POSTGRES_USER", + "value": "postgres", + }, + { + "name": "POSTGRES_PASSWORD", + "value": "postgres", + }, }, "resources": map[string]interface{}{ "requests": map[string]interface{}{ @@ -86,26 +88,24 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { "volumeMounts": []map[string]interface{}{ { "name": "datadir", - "mountPath": "/cockroach/cockroach-data", + "mountPath": "/var/lib/postgresql/data", }, }, "ports": []map[string]interface{}{ - { - "name": "http", - "protocol": "TCP", - "containerPort": 8080, - }, { "name": "grpc", "protocol": "TCP", - "containerPort": 26257, + "containerPort": 5432, }, }, "readinessProbe": map[string]interface{}{ - "httpGet": map[string]interface{}{ - "path": "/health?ready=1", - "port": "http", - "scheme": "HTTP", + "exec": map[string]interface{}{ + "command": []string{ + "/bin/sh", + "-c", + "-e", + "exec pg_isready -U postgres -h 127.0.0.1 -p 5432", + }, }, "initialDelaySeconds": 10, "periodSeconds": 5, diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index 89418f6..e8ca7ca 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -32,7 +32,7 @@ func New( ) (*Manager, error) { client, err := k8s.New() if err != nil { - return nil, fmt.Errorf("failed to create cockroach client: %+v", err) + return nil, fmt.Errorf("failed to create postgres client: %+v", err) } ctx, cancel := context.WithCancel(context.Background()) @@ -94,12 +94,11 @@ func (m *Manager) refresh() { m.state.Apply(update) m.debouncer.Trigger() case <-m.debouncer.Wait(): - log.Info().Msg("Updating cockroach database state") + log.Info().Msg("Updating postgres database state") m.refreshDatabaseState() - log.Info().Msg("Processing cockroach databases started") + log.Info().Msg("Processing postgres databases started") m.processCockroachClients() - m.processCockroachMigrations() - log.Info().Msg("Processing cockroach databases finished") + log.Info().Msg("Processing postgres databases finished") } } @@ -107,16 +106,16 @@ func (m *Manager) refreshDatabaseState() { m.state.ClearRemote() for _, client := range m.state.GetActiveClients() { - cli, err := database.New(client.DBRef.Name, client.DBRef.Namespace) + cli, err := database.New(client.Cluster.Name, client.Cluster.Namespace) if err != nil { - log.Error().Err(err).Msgf("Failed to create client for database %s/%s", client.DBRef.Namespace, client.DBRef.Name) + log.Error().Err(err).Msgf("Failed to create client for database %s/%s", client.Cluster.Namespace, client.Cluster.Name) continue } defer cli.Stop() users, err := cli.ListUsers() if err != nil { - log.Error().Err(err).Msgf("Failed to list users in %s/%s", client.DBRef.Namespace, client.DBRef.Name) + log.Error().Err(err).Msgf("Failed to list users in %s/%s", client.Cluster.Namespace, client.Cluster.Name) continue } @@ -124,7 +123,7 @@ func (m *Manager) refreshDatabaseState() { names, err := cli.ListDBs() if err != nil { - log.Error().Err(err).Msgf("Failed to list databases in %s/%s", client.DBRef.Namespace, client.DBRef.Name) + log.Error().Err(err).Msgf("Failed to list databases in %s/%s", client.Cluster.Namespace, client.Cluster.Name) continue } @@ -133,37 +132,37 @@ func (m *Manager) refreshDatabaseState() { permissions, err := cli.ListPermitted(db) if err != nil { - log.Error().Err(err).Msgf("Failed to list permissions in %s/%s/%s", client.DBRef.Namespace, client.DBRef.Name, db.Name) + log.Error().Err(err).Msgf("Failed to list permissions in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) continue } m.state.Apply(k8s_generic.Update[database.Permission]{ToAdd: permissions}) - mClient, err := database.NewMigrations(db.DB.Name, db.DB.Namespace, db.Name) - if err != nil { - log.Error().Err(err).Msgf("Failed to get migration client in %s/%s/%s", client.DBRef.Namespace, client.DBRef.Name, db.Name) - continue - } - defer mClient.Stop() - - if ok, err := mClient.HasMigrationsTable(); err != nil { - log.Error().Err(err).Msgf("Failed to check for migrations table in %s/%s/%s", client.DBRef.Namespace, client.DBRef.Name, db.Name) - continue - } else if !ok { - err = mClient.CreateMigrationsTable() - if err != nil { - log.Error().Err(err).Msgf("Failed to create migrations table in %s/%s/%s", client.DBRef.Namespace, client.DBRef.Name, db.Name) - continue - } - } - - migrations, err := mClient.AppliedMigrations() - if err != nil { - log.Error().Err(err).Msgf("Failed to get applied migrations in %s/%s/%s", client.DBRef.Namespace, client.DBRef.Name, db.Name) - continue - } - - m.state.Apply(k8s_generic.Update[database.Migration]{ToAdd: migrations}) + // mClient, err := database.NewMigrations(db.Cluster.Name, db.Cluster.Namespace, db.Name) + // if err != nil { + // log.Error().Err(err).Msgf("Failed to get migration client in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) + // continue + // } + // defer mClient.Stop() + + // if ok, err := mClient.HasMigrationsTable(); err != nil { + // log.Error().Err(err).Msgf("Failed to check for migrations table in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) + // continue + // } else if !ok { + // err = mClient.CreateMigrationsTable() + // if err != nil { + // log.Error().Err(err).Msgf("Failed to create migrations table in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) + // continue + // } + // } + + // migrations, err := mClient.AppliedMigrations() + // if err != nil { + // log.Error().Err(err).Msgf("Failed to get applied migrations in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) + // continue + // } + + // m.state.Apply(k8s_generic.Update[database.Migration]{ToAdd: migrations}) } } } @@ -176,22 +175,22 @@ func (m *Manager) processCockroachClients() { dbs := newConsolidator() for _, db := range dbDemand.ToAdd { - dbs.add(db.Target.DB.Name, db.Target.DB.Namespace) + dbs.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) } for _, db := range dbDemand.ToRemove { - dbs.add(db.Target.DB.Name, db.Target.DB.Namespace) + dbs.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) } for _, user := range userDemand.ToAdd { - dbs.add(user.Target.DB.Name, user.Target.DB.Namespace) + dbs.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) } for _, user := range userDemand.ToRemove { - dbs.add(user.Target.DB.Name, user.Target.DB.Namespace) + dbs.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) } for _, perm := range permsDemand.ToAdd { - dbs.add(perm.Target.DB.Name, perm.Target.DB.Namespace) + dbs.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) } for _, perm := range permsDemand.ToRemove { - dbs.add(perm.Target.DB.Name, perm.Target.DB.Namespace) + dbs.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) } for _, secret := range secretsDemand.ToRemove { @@ -212,81 +211,88 @@ func (m *Manager) processCockroachClients() { defer cli.Stop() for _, perm := range permsDemand.ToRemove { - if perm.Target.DB.Name != db || perm.Target.DB.Namespace != namespace { + if perm.Target.Cluster.Name != db || perm.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.DB) + log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) err = cli.RevokePermission(perm.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to revoke permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.DB) + log.Error().Err(err).Msgf("Failed to revoke permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) } } for _, toRemove := range dbDemand.ToRemove { - if toRemove.Target.DB.Name != db || toRemove.Target.DB.Namespace != namespace { + if toRemove.Target.Cluster.Name != db || toRemove.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Dropping database %s in db %s", toRemove.Target.Name, toRemove.Target.DB) + log.Info().Msgf("Dropping database %s in db %s", toRemove.Target.Name, toRemove.Target.Cluster) err = cli.DeleteDB(toRemove.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to delete database %s in db %s", toRemove.Target.Name, toRemove.Target.DB) + log.Error().Err(err).Msgf("Failed to delete database %s in db %s", toRemove.Target.Name, toRemove.Target.Cluster) } } for _, user := range userDemand.ToRemove { - if user.Target.DB.Name != db || user.Target.DB.Namespace != namespace { + if user.Target.Cluster.Name != db || user.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Dropping user %s in db %s", user.Target.Name, user.Target.DB) + log.Info().Msgf("Dropping user %s in db %s", user.Target.Name, user.Target.Cluster) err = cli.DeleteUser(user.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to delete user %s in db %s", user.Target.Name, user.Target.DB) + log.Error().Err(err).Msgf("Failed to delete user %s in db %s", user.Target.Name, user.Target.Cluster) } } for _, toAdd := range dbDemand.ToAdd { - if toAdd.Target.DB.Name != db || toAdd.Target.DB.Namespace != namespace { + if toAdd.Target.Cluster.Name != db || toAdd.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Creating database %s in db %s", toAdd.Target.Name, toAdd.Target.DB) + log.Info().Msgf("Creating database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) err := cli.CreateDB(toAdd.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to create database %s in db %s", toAdd.Target.Name, toAdd.Target.DB) + log.Error().Err(err).Msgf("Failed to create database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) } } for _, user := range userDemand.ToAdd { - if user.Target.DB.Name != db || user.Target.DB.Namespace != namespace { + if user.Target.Cluster.Name != db || user.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Creating user %s in db %s", user.Target.Name, user.Target.DB) + log.Info().Msgf("Creating user %s in db %s", user.Target.Name, user.Target.Cluster) err := cli.CreateUser(user.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to create user %s in db %s", user.Target.Name, user.Target.DB) + log.Error().Err(err).Msgf("Failed to create user %s in db %s", user.Target.Name, user.Target.Cluster) } } for _, perm := range permsDemand.ToAdd { - if perm.Target.DB.Name != db || perm.Target.DB.Namespace != namespace { + if perm.Target.Cluster.Name != db || perm.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.DB) + log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) err := cli.GrantPermission(perm.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to grant permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.DB) + log.Error().Err(err).Msgf("Failed to grant permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) } } } } for _, secret := range secretsDemand.ToAdd { + user, ok := m.state.users.Get(secret.Target.User, secret.Target.Namespace) + if !ok { + log.Error().Msgf("Failed to find user %s in namespace %s for secret %s", secret.Target.User, secret.Target.Namespace, secret.Target.Name) + continue + } + secret.Target.Password = user.Password + log.Info().Msgf("Adding secret %s", secret.Target.Name) err := m.client.Secrets().Create(m.ctx, secret.Target) if err != nil { @@ -294,47 +300,3 @@ func (m *Manager) processCockroachClients() { } } } - -func (m *Manager) processCockroachMigrations() { - demand := m.state.GetMigrationsDemand() - - for _, namespace := range demand.GetNamespaces() { - for _, deployment := range demand.GetDBs(namespace) { - for _, db := range demand.GetDatabases(namespace, deployment) { - if !demand.Next(namespace, deployment, db) { - continue - } - - client, err := database.NewMigrations(deployment, namespace, db) - if err != nil { - log.Error().Err(err).Msgf("Failed to create migrations client for %s", db) - continue - } - defer client.Stop() - - if ok, err := client.HasMigrationsTable(); err != nil { - log.Error().Err(err).Msgf("Failed to check for existing migrations table in %s", db) - continue - } else if !ok { - err = client.CreateMigrationsTable() - if err != nil { - log.Error().Err(err).Msgf("Failed to create migrations table in %s", db) - continue - } - } - - for demand.Next(namespace, deployment, db) { - migration, index := demand.GetNextMigration(namespace, deployment, db) - - log.Info().Msgf("Running migration %s [%s] %d", deployment, db, index) - - err := client.RunMigration(index, migration) - if err != nil { - log.Error().Err(err).Msgf("Failed to run migration %s [%s] %d", deployment, db, index) - break - } - } - } - } - } -} diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index bf147ec..916d0cc 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -2,7 +2,9 @@ package database import ( "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" - "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -10,26 +12,23 @@ import ( ) type State struct { - clients bucket.Bucket[k8s.CockroachClient, *k8s.CockroachClient] - statefulSets bucket.Bucket[k8s.CockroachStatefulSet, *k8s.CockroachStatefulSet] - secrets bucket.Bucket[k8s.CockroachSecret, *k8s.CockroachSecret] - migrations bucket.Bucket[k8s.CockroachMigration, *k8s.CockroachMigration] - databases bucket.Bucket[database.Database, *database.Database] - users bucket.Bucket[database.User, *database.User] - permissions bucket.Bucket[database.Permission, *database.Permission] - applied bucket.Bucket[database.Migration, *database.Migration] + clients bucket.Bucket[clients.Resource] + statefulSets bucket.Bucket[stateful_sets.Resource] + secrets bucket.Bucket[secrets.Resource] + databases bucket.Bucket[database.Database] + users bucket.Bucket[database.User] + permissions bucket.Bucket[database.Permission] + applied bucket.Bucket[database.Migration] } func (s *State) Apply(update interface{}) { switch u := update.(type) { - case k8s_generic.Update[k8s.CockroachClient]: + case k8s_generic.Update[clients.Resource]: s.clients.Apply(u) - case k8s_generic.Update[k8s.CockroachStatefulSet]: + case k8s_generic.Update[stateful_sets.Resource]: s.statefulSets.Apply(u) - case k8s_generic.Update[k8s.CockroachSecret]: + case k8s_generic.Update[secrets.Resource]: s.secrets.Apply(u) - case k8s_generic.Update[k8s.CockroachMigration]: - s.migrations.Apply(u) case k8s_generic.Update[database.Database]: s.databases.Apply(u) case k8s_generic.Update[database.User]: @@ -50,8 +49,8 @@ func (s *State) ClearRemote() { s.applied.Clear() } -func (s *State) GetActiveClients() []k8s.CockroachClient { - clients := []k8s.CockroachClient{} +func (s *State) GetActiveClients() []clients.Resource { + clients := []clients.Resource{} for _, client := range s.clients.List() { target := client.GetTarget() @@ -68,95 +67,76 @@ func (s *State) GetActiveClients() []k8s.CockroachClient { return clients } -func (s *State) GetDBDemand() state.Demand[k8s.CockroachClient, database.Database] { +func (s *State) GetDBDemand() state.Demand[clients.Resource, database.Database] { return state.GetServiceBound( s.clients, s.databases, s.statefulSets, - func(client k8s.CockroachClient) database.Database { + func(client clients.Resource) database.Database { return database.Database{ Name: client.Database, - DB: database.DBRef{ - Name: client.DBRef.Name, - Namespace: client.DBRef.Namespace, + Cluster: database.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, }, } }, ) } -func (s *State) GetUserDemand() state.Demand[k8s.CockroachClient, database.User] { +func (s *State) GetUserDemand() state.Demand[clients.Resource, database.User] { return state.GetServiceBound( s.clients, s.users, s.statefulSets, - func(client k8s.CockroachClient) database.User { + func(client clients.Resource) database.User { return database.User{ Name: client.Username, - DB: database.DBRef{ - Name: client.DBRef.Name, - Namespace: client.DBRef.Namespace, + Cluster: database.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, }, } }, ) } -func (s *State) GetPermissionDemand() state.Demand[k8s.CockroachClient, database.Permission] { +func (s *State) GetPermissionDemand() state.Demand[clients.Resource, database.Permission] { return state.GetServiceBound( s.clients, s.permissions, s.statefulSets, - func(client k8s.CockroachClient) database.Permission { + func(client clients.Resource) database.Permission { return database.Permission{ User: client.Username, Database: client.Database, - DB: database.DBRef{ - Name: client.DBRef.Name, - Namespace: client.DBRef.Namespace, + Cluster: database.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, }, } }, ) } -func (s *State) GetSecretsDemand() state.Demand[k8s.CockroachClient, k8s.CockroachSecret] { +func (s *State) GetSecretsDemand() state.Demand[clients.Resource, secrets.Resource] { return state.GetServiceBound( s.clients, s.secrets, s.statefulSets, - func(client k8s.CockroachClient) k8s.CockroachSecret { - return k8s.CockroachSecret{ - CockroachSecretComparable: k8s.CockroachSecretComparable{ + func(client clients.Resource) secrets.Resource { + return secrets.Resource{ + Comparable: secrets.Comparable{ Name: client.Secret, Namespace: client.Namespace, - DB: client.DBRef, - Database: client.Database, - User: client.Username, + Cluster: secrets.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, + }, + Database: client.Database, + User: client.Username, }, } }, ) } - -func (s *State) GetMigrationsDemand() state.DBMigrations { - migrations := state.NewMigrations() - - isReady := func(db string, namespace string) bool { - ss, hasSS := s.statefulSets.Get(db, namespace) - - return hasSS && ss.Ready - } - - for _, m := range s.migrations.List() { - if isReady(m.DBRef.Name, m.DBRef.Namespace) { - migrations.AddRequest(m.DBRef.Namespace, m.DBRef.Name, m.Database, m.Index, m.Migration) - } - } - - for _, m := range s.applied.List() { - migrations.AddApplied(m.DB.Namespace, m.DB.Name, m.Database, m.Index) - } - - return migrations -} diff --git a/internal/dbs/postgres/managers/deployment/manager.go b/internal/dbs/postgres/managers/deployment/manager.go index a6632f2..77c13cc 100644 --- a/internal/dbs/postgres/managers/deployment/manager.go +++ b/internal/dbs/postgres/managers/deployment/manager.go @@ -5,7 +5,12 @@ import ( "fmt" "time" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/pvcs" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/services" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/internal/utils" "github.com/rs/zerolog/log" @@ -27,14 +32,14 @@ func New( ) (*Manager, error) { client, err := k8s.New() if err != nil { - return nil, fmt.Errorf("failed to create cockroach client: %+v", err) + return nil, fmt.Errorf("failed to create postgres client: %+v", err) } ctx, cancel := context.WithCancel(context.Background()) updates := make(chan any) for _, f := range []WatchFunc{ - client.DBs().Watch, + client.Clusters().Watch, client.Clients().Watch, client.StatefulSets().Watch, client.PVCs().Watch, @@ -47,11 +52,11 @@ func New( } state := State{ - dbs: bucket.NewBucket[k8s.CockroachDB](), - clients: bucket.NewBucket[k8s.CockroachClient](), - statefulSets: bucket.NewBucket[k8s.CockroachStatefulSet](), - pvcs: bucket.NewBucket[k8s.CockroachPVC](), - services: bucket.NewBucket[k8s.CockroachService](), + clusters: bucket.NewBucket[clusters.Resource](), + clients: bucket.NewBucket[clients.Resource](), + statefulSets: bucket.NewBucket[stateful_sets.Resource](), + pvcs: bucket.NewBucket[pvcs.Resource](), + services: bucket.NewBucket[services.Resource](), } return &Manager{ @@ -89,13 +94,13 @@ func (m *Manager) refresh() { m.state.Apply(update) m.debouncer.Trigger() case <-m.debouncer.Wait(): - log.Info().Msg("Processing cockroach deployments started") - m.processCockroachDBs() - log.Info().Msg("Processing cockroach deployments finished") + log.Info().Msg("Processing postgres deployments started") + m.processPostgresDBs() + log.Info().Msg("Processing postgres deployments finished") } } -func (m *Manager) processCockroachDBs() { +func (m *Manager) processPostgresDBs() { ssDemand := m.state.GetStatefulSetDemand() svcDemand := m.state.GetServiceDemand() pvcsToRemove := m.state.GetPVCDemand() @@ -105,7 +110,7 @@ func (m *Manager) processCockroachDBs() { err := m.client.StatefulSets().Delete(m.ctx, db.Target.Name, db.Target.Namespace) if err != nil { - log.Error().Err(err).Msgf("Failed to delete cockroachdb stateful set: %+v", err) + log.Error().Err(err).Msgf("Failed to delete postgresdb stateful set: %+v", err) } } @@ -114,7 +119,7 @@ func (m *Manager) processCockroachDBs() { err := m.client.Services().Delete(m.ctx, svc.Target.Name, svc.Target.Namespace) if err != nil { - log.Error().Err(err).Msgf("Failed to delete cockroachdb service: %+v", err) + log.Error().Err(err).Msgf("Failed to delete postgresdb service: %+v", err) } } @@ -123,7 +128,7 @@ func (m *Manager) processCockroachDBs() { err := m.client.PVCs().Delete(m.ctx, pvc.Name, pvc.Namespace) if err != nil { - log.Error().Err(err).Msgf("Failed to delete cockroachdb PVC: %+v", err) + log.Error().Err(err).Msgf("Failed to delete postgresdb PVC: %+v", err) } } @@ -131,10 +136,10 @@ func (m *Manager) processCockroachDBs() { log.Info().Msgf("Creating db: %s", db.Target.Name) err := m.client.StatefulSets().Create(m.ctx, db.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to create cockroachdb stateful set: %+v", err) - m.client.DBs().Event(m.ctx, db.Parent, "Normal", "ProvisioningFailed", fmt.Sprintf("Failed to create stateful set: %s", err.Error())) + log.Error().Err(err).Msgf("Failed to create postgresdb stateful set: %+v", err) + m.client.Clusters().Event(m.ctx, db.Parent, "Normal", "ProvisioningFailed", fmt.Sprintf("Failed to create stateful set: %s", err.Error())) } else { - m.client.DBs().Event(m.ctx, db.Parent, "Normal", "ProvisioningSucceeded", "Created stateful set") + m.client.Clusters().Event(m.ctx, db.Parent, "Normal", "ProvisioningSucceeded", "Created stateful set") } } @@ -143,7 +148,7 @@ func (m *Manager) processCockroachDBs() { err := m.client.Services().Create(m.ctx, svc.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to create cockroachdb service: %+v", err) + log.Error().Err(err).Msgf("Failed to create postgresdb service: %+v", err) } } } diff --git a/internal/dbs/postgres/managers/deployment/state.go b/internal/dbs/postgres/managers/deployment/state.go index 2d25db3..ebb61cc 100644 --- a/internal/dbs/postgres/managers/deployment/state.go +++ b/internal/dbs/postgres/managers/deployment/state.go @@ -1,7 +1,12 @@ package deployment import ( - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/pvcs" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/services" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -9,70 +14,70 @@ import ( ) type State struct { - dbs bucket.Bucket[k8s.CockroachDB, *k8s.CockroachDB] - clients bucket.Bucket[k8s.CockroachClient, *k8s.CockroachClient] - statefulSets bucket.Bucket[k8s.CockroachStatefulSet, *k8s.CockroachStatefulSet] - pvcs bucket.Bucket[k8s.CockroachPVC, *k8s.CockroachPVC] - services bucket.Bucket[k8s.CockroachService, *k8s.CockroachService] - secrets bucket.Bucket[k8s.CockroachSecret, *k8s.CockroachSecret] + clusters bucket.Bucket[clusters.Resource] + clients bucket.Bucket[clients.Resource] + statefulSets bucket.Bucket[stateful_sets.Resource] + pvcs bucket.Bucket[pvcs.Resource] + services bucket.Bucket[services.Resource] + secrets bucket.Bucket[secrets.Resource] } func (s *State) Apply(update interface{}) { switch u := update.(type) { - case k8s_generic.Update[k8s.CockroachDB]: - s.dbs.Apply(u) - case k8s_generic.Update[k8s.CockroachClient]: + case k8s_generic.Update[clusters.Resource]: + s.clusters.Apply(u) + case k8s_generic.Update[clients.Resource]: s.clients.Apply(u) - case k8s_generic.Update[k8s.CockroachStatefulSet]: + case k8s_generic.Update[stateful_sets.Resource]: s.statefulSets.Apply(u) - case k8s_generic.Update[k8s.CockroachPVC]: + case k8s_generic.Update[pvcs.Resource]: s.pvcs.Apply(u) - case k8s_generic.Update[k8s.CockroachService]: + case k8s_generic.Update[services.Resource]: s.services.Apply(u) - case k8s_generic.Update[k8s.CockroachSecret]: + case k8s_generic.Update[secrets.Resource]: s.secrets.Apply(u) default: log.Logger.Error().Interface("update", u).Msg("wat dis? Unknown state update") } } -func (s *State) GetStatefulSetDemand() state.Demand[k8s.CockroachDB, k8s.CockroachStatefulSet] { +func (s *State) GetStatefulSetDemand() state.Demand[clusters.Resource, stateful_sets.Resource] { return state.GetStorageBound( - s.dbs, + s.clusters, s.statefulSets, - func(db k8s.CockroachDB) k8s.CockroachStatefulSet { - return k8s.CockroachStatefulSet{ - CockroachStatefulSetComparable: k8s.CockroachStatefulSetComparable{ - Name: db.Name, - Namespace: db.Namespace, - Storage: db.Storage, + func(cluster clusters.Resource) stateful_sets.Resource { + return stateful_sets.Resource{ + Comparable: stateful_sets.Comparable{ + Name: cluster.Name, + Namespace: cluster.Namespace, + Storage: cluster.Storage, }, } }, ) } -func (s *State) GetServiceDemand() state.Demand[k8s.CockroachDB, k8s.CockroachService] { +func (s *State) GetServiceDemand() state.Demand[clusters.Resource, services.Resource] { return state.GetOneForOne( - s.dbs, + s.clusters, s.services, - func(db k8s.CockroachDB) k8s.CockroachService { - return k8s.CockroachService{ - CockroachServiceComparable: k8s.CockroachServiceComparable{ - Name: db.Name, - Namespace: db.Namespace, + func(cluster clusters.Resource) services.Resource { + return services.Resource{ + Comparable: services.Comparable{ + Name: cluster.Name, + Namespace: cluster.Namespace, }, } }, ) } -func (s *State) GetPVCDemand() []k8s.CockroachPVC { +func (s *State) GetPVCDemand() []pvcs.Resource { return state.GetOrphaned( s.statefulSets, s.pvcs, - func(ss k8s.CockroachStatefulSet, pvc k8s.CockroachPVC) bool { - return ss.Name == pvc.Database + func(ss stateful_sets.Resource, pvc pvcs.Resource) bool { + return ss.Name == pvc.Cluster }, ) } diff --git a/internal/dbs/redis/k8s/client.go b/internal/dbs/redis/k8s/client.go index 2e641cc..9a3acaf 100644 --- a/internal/dbs/redis/k8s/client.go +++ b/internal/dbs/redis/k8s/client.go @@ -3,11 +3,22 @@ package k8s import ( "fmt" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/pvcs" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/services" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" ) type Client struct { - builder *k8s_generic.Builder + clients *k8s_generic.Client[clients.Resource] + clusters *k8s_generic.Client[clusters.Resource] + pvcs *k8s_generic.Client[pvcs.Resource] + secrets *k8s_generic.Client[secrets.Resource] + services *k8s_generic.Client[services.Resource] + stateful_sets *k8s_generic.Client[stateful_sets.Resource] } func New() (*Client, error) { @@ -17,6 +28,35 @@ func New() (*Client, error) { } return &Client{ - builder: builder, + clients: k8s_generic.NewClient(builder, clients.ClientArgs), + clusters: k8s_generic.NewClient(builder, clusters.ClientArgs), + pvcs: k8s_generic.NewClient(builder, pvcs.ClientArgs), + secrets: k8s_generic.NewClient(builder, secrets.ClientArgs), + services: k8s_generic.NewClient(builder, services.ClientArgs), + stateful_sets: k8s_generic.NewClient(builder, stateful_sets.ClientArgs), }, nil } + +func (c *Client) Clients() *k8s_generic.Client[clients.Resource] { + return c.clients +} + +func (c *Client) Clusters() *k8s_generic.Client[clusters.Resource] { + return c.clusters +} + +func (c *Client) PVCs() *k8s_generic.Client[pvcs.Resource] { + return c.pvcs +} + +func (c *Client) Secrets() *k8s_generic.Client[secrets.Resource] { + return c.secrets +} + +func (c *Client) Services() *k8s_generic.Client[services.Resource] { + return c.services +} + +func (c *Client) StatefulSets() *k8s_generic.Client[stateful_sets.Resource] { + return c.stateful_sets +} diff --git a/internal/dbs/redis/k8s/clients.go b/internal/dbs/redis/k8s/clients.go deleted file mode 100644 index 498fcf2..0000000 --- a/internal/dbs/redis/k8s/clients.go +++ /dev/null @@ -1,123 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type RedisClientComparable struct { - Name string - Namespace string - DBRef DBRef - Unit int64 - Secret string -} - -type RedisClient struct { - RedisClientComparable - UID string - ResourceVersion string -} - -func (cli RedisClient) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "RedisClient", - "metadata": map[string]interface{}{ - "name": cli.Name, - "namespace": cli.Namespace, - }, - "spec": map[string]interface{}{ - "dbRef": map[string]interface{}{ - "name": cli.DBRef.Name, - "namespace": cli.DBRef.Namespace, - }, - "unit": cli.Unit, - "secret": cli.Secret, - }, - }) - - return result -} - -func redisClientFromUnstructured(obj *unstructured.Unstructured) (RedisClient, error) { - var err error - cli := RedisClient{} - - cli.Name = obj.GetName() - cli.Namespace = obj.GetNamespace() - cli.UID = string(obj.GetUID()) - cli.ResourceVersion = obj.GetResourceVersion() - - cli.DBRef.Name, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "name") - if err != nil { - return cli, fmt.Errorf("failed to get db ref name: %+v", err) - } - - cli.DBRef.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "dbRef", "namespace") - if err != nil { - return cli, fmt.Errorf("failed to get db ref namespace: %+v", err) - } - - cli.Unit, err = k8s_generic.GetProperty[int64](obj, "spec", "unit") - if err != nil { - return cli, fmt.Errorf("failed to get unit: %+v", err) - } - - cli.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") - if err != nil { - return cli, fmt.Errorf("failed to get secret: %+v", err) - } - - return cli, nil -} - -func (cli RedisClient) GetName() string { - return cli.Name -} - -func (cli RedisClient) GetNamespace() string { - return cli.Namespace -} - -func (cli RedisClient) GetUID() string { - return cli.UID -} - -func (cli RedisClient) GetResourceVersion() string { - return cli.ResourceVersion -} - -func (cli RedisClient) GetTarget() string { - return cli.DBRef.Name -} - -func (cli RedisClient) GetTargetNamespace() string { - return cli.DBRef.Namespace -} - -func (cli RedisClient) Equal(obj k8s_generic.Resource) bool { - redisObj, ok := obj.(*RedisClient) - if !ok { - return false - } - return cli.RedisClientComparable == redisObj.RedisClientComparable -} - -func (c *Client) Clients() *k8s_generic.Client[RedisClient] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "redisclients", - }, - "RedisClient", - nil, - redisClientFromUnstructured, - ) -} diff --git a/internal/dbs/redis/k8s/clients/clients.go b/internal/dbs/redis/k8s/clients/clients.go new file mode 100644 index 0000000..ea0e110 --- /dev/null +++ b/internal/dbs/redis/k8s/clients/clients.go @@ -0,0 +1,124 @@ +package clients + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "ponglehub.co.uk", + Version: "v1alpha1", + Resource: "redisclients", + }, + Kind: "RedisClient", + FromUnstructured: fromUnstructured, +} + +type Cluster struct { + Name string + Namespace string +} + +type Comparable struct { + Name string + Namespace string + Cluster Cluster + Unit int64 + Secret string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + result := &unstructured.Unstructured{} + result.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "ponglehub.co.uk/v1alpha1", + "kind": "RedisClient", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + }, + "spec": map[string]interface{}{ + "cluster": map[string]interface{}{ + "name": r.Cluster.Name, + "namespace": r.Cluster.Namespace, + }, + "unit": r.Unit, + "secret": r.Secret, + }, + }) + + return result +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + var err error + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + r.Cluster.Name, err = k8s_generic.GetProperty[string](obj, "spec", "cluster", "name") + if err != nil { + return r, fmt.Errorf("failed to get cluster name: %+v", err) + } + + r.Cluster.Namespace, err = k8s_generic.GetProperty[string](obj, "spec", "cluster", "namespace") + if err != nil { + return r, fmt.Errorf("failed to get cluster namespace: %+v", err) + } + + r.Unit, err = k8s_generic.GetProperty[int64](obj, "spec", "unit") + if err != nil { + return r, fmt.Errorf("failed to get unit: %+v", err) + } + + r.Secret, err = k8s_generic.GetProperty[string](obj, "spec", "secret") + if err != nil { + return r, fmt.Errorf("failed to get secret: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) GetTarget() string { + return r.Cluster.Name +} + +func (r Resource) GetTargetNamespace() string { + return r.Cluster.Namespace +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/redis/k8s/clusters/clusters.go b/internal/dbs/redis/k8s/clusters/clusters.go new file mode 100644 index 0000000..49b5939 --- /dev/null +++ b/internal/dbs/redis/k8s/clusters/clusters.go @@ -0,0 +1,92 @@ +package clusters + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "ponglehub.co.uk", + Version: "v1alpha1", + Resource: "redisclusters", + }, + Kind: "RedisCluster", + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string + Storage string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + result := &unstructured.Unstructured{} + result.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "ponglehub.co.uk/v1alpha1", + "kind": "RedisCluster", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + }, + "spec": map[string]interface{}{ + "storage": r.Storage, + }, + }) + + return result +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + var err error + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + r.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "storage") + if err != nil { + return r, fmt.Errorf("failed to get storage: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetStorage() string { + return r.Storage +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/redis/k8s/db_ref.go b/internal/dbs/redis/k8s/db_ref.go deleted file mode 100644 index 396a672..0000000 --- a/internal/dbs/redis/k8s/db_ref.go +++ /dev/null @@ -1,6 +0,0 @@ -package k8s - -type DBRef struct { - Name string - Namespace string -} diff --git a/internal/dbs/redis/k8s/dbs.go b/internal/dbs/redis/k8s/dbs.go deleted file mode 100644 index a533188..0000000 --- a/internal/dbs/redis/k8s/dbs.go +++ /dev/null @@ -1,96 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type RedisDBComparable struct { - Name string - Namespace string - Storage string -} - -type RedisDB struct { - RedisDBComparable - UID string - ResourceVersion string -} - -func (db RedisDB) ToUnstructured() *unstructured.Unstructured { - result := &unstructured.Unstructured{} - result.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "ponglehub.co.uk/v1alpha1", - "kind": "RedisDB", - "metadata": map[string]interface{}{ - "name": db.Name, - "namespace": db.Namespace, - }, - "spec": map[string]interface{}{ - "storage": db.Storage, - }, - }) - - return result -} - -func redisDBFromUnstructured(obj *unstructured.Unstructured) (RedisDB, error) { - var err error - db := RedisDB{} - - db.Name = obj.GetName() - db.Namespace = obj.GetNamespace() - db.UID = string(obj.GetUID()) - db.ResourceVersion = obj.GetResourceVersion() - db.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "storage") - if err != nil { - return db, fmt.Errorf("failed to get storage: %+v", err) - } - - return db, nil -} - -func (db RedisDB) GetName() string { - return db.Name -} - -func (db RedisDB) GetNamespace() string { - return db.Namespace -} - -func (db RedisDB) GetStorage() string { - return db.Storage -} - -func (db RedisDB) GetUID() string { - return db.UID -} - -func (db RedisDB) GetResourceVersion() string { - return db.ResourceVersion -} - -func (db RedisDB) Equal(obj k8s_generic.Resource) bool { - redisDB, ok := obj.(*RedisDB) - if !ok { - return false - } - return db.RedisDBComparable == redisDB.RedisDBComparable -} - -func (c *Client) DBs() *k8s_generic.Client[RedisDB] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "ponglehub.co.uk", - Version: "v1alpha1", - Resource: "redisdbs", - }, - "RedisDB", - nil, - redisDBFromUnstructured, - ) -} diff --git a/internal/dbs/redis/k8s/pvcs.go b/internal/dbs/redis/k8s/pvcs.go deleted file mode 100644 index 89a26f2..0000000 --- a/internal/dbs/redis/k8s/pvcs.go +++ /dev/null @@ -1,88 +0,0 @@ -package k8s - -import ( - "fmt" - - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type RedisPVCComparable struct { - Name string - Namespace string - Database string - Storage string -} - -type RedisPVC struct { - RedisPVCComparable - UID string - ResourceVersion string -} - -func (p RedisPVC) ToUnstructured() *unstructured.Unstructured { - panic("not implemented") -} - -func redisPVCFromUnstructured(obj *unstructured.Unstructured) (RedisPVC, error) { - var err error - p := RedisPVC{} - - p.Name = obj.GetName() - p.Namespace = obj.GetNamespace() - p.UID = string(obj.GetUID()) - p.ResourceVersion = obj.GetResourceVersion() - p.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "resources", "requests", "storage") - if err != nil { - return p, fmt.Errorf("failed to get storage: %+v", err) - } - - p.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") - if err != nil { - return p, fmt.Errorf("failed to get database from app label: %+v", err) - } - - return p, nil -} - -func (p RedisPVC) GetName() string { - return p.Name -} - -func (p RedisPVC) GetNamespace() string { - return p.Namespace -} - -func (p RedisPVC) GetUID() string { - return p.UID -} - -func (p RedisPVC) GetResourceVersion() string { - return p.ResourceVersion -} - -func (p RedisPVC) Equal(obj k8s_generic.Resource) bool { - redisPVC, ok := obj.(*RedisPVC) - if !ok { - return false - } - return p.RedisPVCComparable == redisPVC.RedisPVCComparable -} - -func (c *Client) PVCs() *k8s_generic.Client[RedisPVC] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "persistentvolumeclaims", - }, - "PersistentVolumeClaim", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "redis", - }, common.LABEL_FILTERS), - redisPVCFromUnstructured, - ) -} diff --git a/internal/dbs/redis/k8s/pvcs/pvcs.go b/internal/dbs/redis/k8s/pvcs/pvcs.go new file mode 100644 index 0000000..34e5e03 --- /dev/null +++ b/internal/dbs/redis/k8s/pvcs/pvcs.go @@ -0,0 +1,85 @@ +package pvcs + +import ( + "fmt" + + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "persistentvolumeclaims", + }, + Kind: "PersistentVolumeClaim", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "redis", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string + Database string + Storage string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + panic("not implemented") +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + var err error + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + r.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "resources", "requests", "storage") + if err != nil { + return r, fmt.Errorf("failed to get storage: %+v", err) + } + + r.Database, err = k8s_generic.GetProperty[string](obj, "metadata", "labels", "app") + if err != nil { + return r, fmt.Errorf("failed to get database from app label: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/redis/k8s/secrets.go b/internal/dbs/redis/k8s/secrets.go deleted file mode 100644 index d3483c7..0000000 --- a/internal/dbs/redis/k8s/secrets.go +++ /dev/null @@ -1,127 +0,0 @@ -package k8s - -import ( - "encoding/base64" - "fmt" - "strconv" - "strings" - - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type RedisSecretComparable struct { - Name string - Namespace string - DB DBRef - Unit int -} - -type RedisSecret struct { - RedisSecretComparable - UID string - ResourceVersion string -} - -func (s RedisSecret) GetHost() string { - return fmt.Sprintf("%s.%s.svc.cluster.local", s.DB.Name, s.DB.Namespace) -} - -func encode(data string) string { - return base64.StdEncoding.EncodeToString([]byte(data)) -} - -func (s RedisSecret) ToUnstructured() *unstructured.Unstructured { - secret := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, - "labels": k8s_generic.Merge(map[string]string{ - "app": s.Name, - "ponglehub.co.uk/resource-type": "redis", - }, common.LABEL_FILTERS), - }, - "data": map[string]interface{}{ - "REDIS_HOST": encode(s.GetHost()), - "REDIS_PORT": encode("6379"), - "REDIS_UNIT": encode(strconv.FormatInt(int64(s.Unit), 10)), - }, - }, - } - - return secret -} - -func redisSecretFromUnstructured(obj *unstructured.Unstructured) (RedisSecret, error) { - s := RedisSecret{} - - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() - - hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "REDIS_HOST") - if err != nil { - return s, fmt.Errorf("failed to get REDIS_HOST: %+v", err) - } - s.DB.Name = strings.Split(hostname, ".")[0] - s.DB.Namespace = strings.Split(hostname, ".")[1] - - unit, err := k8s_generic.GetEncodedProperty(obj, "data", "REDIS_UNIT") - if err != nil { - return s, fmt.Errorf("failed to get REDIS_UNIT: %+v", err) - } - - s.Unit, err = strconv.Atoi(unit) - if err != nil { - return s, fmt.Errorf("failed to parse REDIS_UNIT: %+v", err) - } - - return s, nil -} - -func (s RedisSecret) GetName() string { - return s.Name -} - -func (s RedisSecret) GetNamespace() string { - return s.Namespace -} - -func (s RedisSecret) GetUID() string { - return s.UID -} - -func (s RedisSecret) GetResourceVersion() string { - return s.ResourceVersion -} - -func (s RedisSecret) Equal(obj k8s_generic.Resource) bool { - redisSecret, ok := obj.(*RedisSecret) - if !ok { - return false - } - return s.RedisSecretComparable == redisSecret.RedisSecretComparable -} - -func (c *Client) Secrets() *k8s_generic.Client[RedisSecret] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "secrets", - }, - "Secret", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "redis", - }, common.LABEL_FILTERS), - redisSecretFromUnstructured, - ) -} diff --git a/internal/dbs/redis/k8s/secrets/secrets.go b/internal/dbs/redis/k8s/secrets/secrets.go new file mode 100644 index 0000000..1fcbeab --- /dev/null +++ b/internal/dbs/redis/k8s/secrets/secrets.go @@ -0,0 +1,129 @@ +package secrets + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "secrets", + }, + Kind: "Secret", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "redis", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Cluster struct { + Name string + Namespace string +} + +type Comparable struct { + Name string + Namespace string + Cluster Cluster + Unit int +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) GetHost() string { + return fmt.Sprintf("%s.%s.svc.cluster.local", r.Cluster.Name, r.Cluster.Namespace) +} + +func encode(data string) string { + return base64.StdEncoding.EncodeToString([]byte(data)) +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + "labels": k8s_generic.Merge(map[string]string{ + "app": r.Name, + "ponglehub.co.uk/resource-type": "redis", + }, common.LABEL_FILTERS), + }, + "data": map[string]interface{}{ + "REDIS_HOST": encode(r.GetHost()), + "REDIS_PORT": encode("6379"), + "REDIS_UNIT": encode(strconv.FormatInt(int64(r.Unit), 10)), + }, + }, + } + + return secret +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + hostname, err := k8s_generic.GetEncodedProperty(obj, "data", "REDIS_HOST") + if err != nil { + return r, fmt.Errorf("failed to get REDIS_HOST: %+v", err) + } + r.Cluster.Name = strings.Split(hostname, ".")[0] + r.Cluster.Namespace = strings.Split(hostname, ".")[1] + + unit, err := k8s_generic.GetEncodedProperty(obj, "data", "REDIS_UNIT") + if err != nil { + return r, fmt.Errorf("failed to get REDIS_UNIT: %+v", err) + } + + r.Unit, err = strconv.Atoi(unit) + if err != nil { + return r, fmt.Errorf("failed to parse REDIS_UNIT: %+v", err) + } + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/redis/k8s/services.go b/internal/dbs/redis/k8s/services.go deleted file mode 100644 index 863fc35..0000000 --- a/internal/dbs/redis/k8s/services.go +++ /dev/null @@ -1,102 +0,0 @@ -package k8s - -import ( - "github.com/benjamin-wright/db-operator/internal/common" - "github.com/benjamin-wright/db-operator/pkg/k8s_generic" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type RedisServiceComparable struct { - Name string - Namespace string -} - -type RedisService struct { - RedisServiceComparable - UID string - ResourceVersion string -} - -func (s RedisService) ToUnstructured() *unstructured.Unstructured { - statefulset := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Service", - "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, - "labels": k8s_generic.Merge(map[string]string{ - "app": s.Name, - "ponglehub.co.uk/resource-type": "redis", - }, common.LABEL_FILTERS), - }, - "spec": map[string]interface{}{ - "ports": []map[string]interface{}{ - { - "name": "tcp", - "port": 6379, - "protocol": "TCP", - "targetPort": "tcp", - }, - }, - "selector": map[string]interface{}{ - "app": s.Name, - }, - }, - }, - } - - return statefulset -} - -func redisServiceFromUnstructured(obj *unstructured.Unstructured) (RedisService, error) { - s := RedisService{} - - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() - - return s, nil -} - -func (s RedisService) GetName() string { - return s.Name -} - -func (s RedisService) GetNamespace() string { - return s.Namespace -} - -func (s RedisService) GetUID() string { - return s.UID -} - -func (s RedisService) GetResourceVersion() string { - return s.ResourceVersion -} - -func (s RedisService) Equal(obj k8s_generic.Resource) bool { - redisService, ok := obj.(*RedisService) - if !ok { - return false - } - return s.RedisServiceComparable == redisService.RedisServiceComparable -} - -func (c *Client) Services() *k8s_generic.Client[RedisService] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "", - Version: "v1", - Resource: "services", - }, - "Service", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "redis", - }, common.LABEL_FILTERS), - redisServiceFromUnstructured, - ) -} diff --git a/internal/dbs/redis/k8s/services/services.go b/internal/dbs/redis/k8s/services/services.go new file mode 100644 index 0000000..b2f290d --- /dev/null +++ b/internal/dbs/redis/k8s/services/services.go @@ -0,0 +1,99 @@ +package services + +import ( + "github.com/benjamin-wright/db-operator/internal/common" + "github.com/benjamin-wright/db-operator/pkg/k8s_generic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "services", + }, + Kind: "Service", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "redis", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { + Name string + Namespace string +} + +type Resource struct { + Comparable + UID string + ResourceVersion string +} + +func (r Resource) ToUnstructured() *unstructured.Unstructured { + statefulset := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": r.Name, + "namespace": r.Namespace, + "labels": k8s_generic.Merge(map[string]string{ + "app": r.Name, + "ponglehub.co.uk/resource-type": "redis", + }, common.LABEL_FILTERS), + }, + "spec": map[string]interface{}{ + "ports": []map[string]interface{}{ + { + "name": "tcp", + "port": 6379, + "protocol": "TCP", + "targetPort": "tcp", + }, + }, + "selector": map[string]interface{}{ + "app": r.Name, + }, + }, + }, + } + + return statefulset +} + +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { + r := Resource{} + + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() + + return r, nil +} + +func (r Resource) GetName() string { + return r.Name +} + +func (r Resource) GetNamespace() string { + return r.Namespace +} + +func (r Resource) GetUID() string { + return r.UID +} + +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion +} + +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) + if !ok { + return false + } + return r.Comparable == other.Comparable +} diff --git a/internal/dbs/redis/k8s/stateful_sets.go b/internal/dbs/redis/k8s/stateful_sets/stateful_sets.go similarity index 64% rename from internal/dbs/redis/k8s/stateful_sets.go rename to internal/dbs/redis/k8s/stateful_sets/stateful_sets.go index e443517..b253d25 100644 --- a/internal/dbs/redis/k8s/stateful_sets.go +++ b/internal/dbs/redis/k8s/stateful_sets/stateful_sets.go @@ -1,4 +1,4 @@ -package k8s +package stateful_sets import ( "fmt" @@ -9,27 +9,40 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -type RedisStatefulSetComparable struct { +var ClientArgs = k8s_generic.ClientArgs[Resource]{ + Schema: schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "statefulsets", + }, + Kind: "StatefulSet", + LabelFilters: k8s_generic.Merge(map[string]string{ + "ponglehub.co.uk/resource-type": "redis", + }, common.LABEL_FILTERS), + FromUnstructured: fromUnstructured, +} + +type Comparable struct { Name string Namespace string Storage string Ready bool } -type RedisStatefulSet struct { - RedisStatefulSetComparable +type Resource struct { + Comparable UID string ResourceVersion string } -func (s RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { +func (r Resource) ToUnstructured() *unstructured.Unstructured { statefulset := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "StatefulSet", "metadata": map[string]interface{}{ - "name": s.Name, - "namespace": s.Namespace, + "name": r.Name, + "namespace": r.Namespace, "labels": k8s_generic.Merge(map[string]string{ "ponglehub.co.uk/resource-type": "redis", }, common.LABEL_FILTERS), @@ -38,13 +51,13 @@ func (s RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { "replicas": 1, "selector": map[string]interface{}{ "matchLabels": map[string]interface{}{ - "app": s.Name, + "app": r.Name, }, }, "template": map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "app": s.Name, + "app": r.Name, }, }, @@ -112,7 +125,7 @@ func (s RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { }, "resources": map[string]interface{}{ "requests": map[string]interface{}{ - "storage": s.Storage, + "storage": r.Storage, }, }, }, @@ -125,18 +138,18 @@ func (s RedisStatefulSet) ToUnstructured() *unstructured.Unstructured { return statefulset } -func redisStatefulSetFromUnstructured(obj *unstructured.Unstructured) (RedisStatefulSet, error) { +func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { var err error - s := RedisStatefulSet{} + r := Resource{} - s.Name = obj.GetName() - s.Namespace = obj.GetNamespace() - s.UID = string(obj.GetUID()) - s.ResourceVersion = obj.GetResourceVersion() + r.Name = obj.GetName() + r.Namespace = obj.GetNamespace() + r.UID = string(obj.GetUID()) + r.ResourceVersion = obj.GetResourceVersion() - s.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "volumeClaimTemplates", "0", "spec", "resources", "requests", "storage") + r.Storage, err = k8s_generic.GetProperty[string](obj, "spec", "volumeClaimTemplates", "0", "spec", "resources", "requests", "storage") if err != nil { - return s, fmt.Errorf("failed to get storage: %+v", err) + return r, fmt.Errorf("failed to get storage: %+v", err) } replicas, err := k8s_generic.GetProperty[int64](obj, "status", "replicas") @@ -149,55 +162,39 @@ func redisStatefulSetFromUnstructured(obj *unstructured.Unstructured) (RedisStat readyReplicas = 0 } - s.Ready = replicas > 0 && replicas == readyReplicas + r.Ready = replicas > 0 && replicas == readyReplicas - return s, nil + return r, nil } -func (db RedisStatefulSet) GetName() string { - return db.Name +func (r Resource) GetName() string { + return r.Name } -func (db RedisStatefulSet) GetNamespace() string { - return db.Namespace +func (r Resource) GetNamespace() string { + return r.Namespace } -func (db RedisStatefulSet) GetUID() string { - return db.UID +func (r Resource) GetUID() string { + return r.UID } -func (db RedisStatefulSet) GetResourceVersion() string { - return db.ResourceVersion +func (r Resource) GetResourceVersion() string { + return r.ResourceVersion } -func (db RedisStatefulSet) GetStorage() string { - return db.Storage +func (r Resource) GetStorage() string { + return r.Storage } -func (db RedisStatefulSet) IsReady() bool { - return db.Ready +func (r Resource) IsReady() bool { + return r.Ready } -func (db RedisStatefulSet) Equal(obj k8s_generic.Resource) bool { - other, ok := obj.(*RedisStatefulSet) +func (r Resource) Equal(obj k8s_generic.Resource) bool { + other, ok := obj.(*Resource) if !ok { return false } - return db.RedisStatefulSetComparable == other.RedisStatefulSetComparable -} - -func (c *Client) StatefulSets() *k8s_generic.Client[RedisStatefulSet] { - return k8s_generic.NewClient( - c.builder, - schema.GroupVersionResource{ - Group: "apps", - Version: "v1", - Resource: "statefulsets", - }, - "StatefulSet", - k8s_generic.Merge(map[string]string{ - "ponglehub.co.uk/resource-type": "redis", - }, common.LABEL_FILTERS), - redisStatefulSetFromUnstructured, - ) + return r.Comparable == other.Comparable } diff --git a/internal/dbs/redis/manager/manager.go b/internal/dbs/redis/manager/manager.go index 1981e65..e3a7f71 100644 --- a/internal/dbs/redis/manager/manager.go +++ b/internal/dbs/redis/manager/manager.go @@ -6,6 +6,12 @@ import ( "time" "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/pvcs" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/services" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/internal/utils" "github.com/rs/zerolog/log" @@ -34,7 +40,7 @@ func New( updates := make(chan any) for _, f := range []WatchFunc{ - client.DBs().Watch, + client.Clusters().Watch, client.Clients().Watch, client.StatefulSets().Watch, client.PVCs().Watch, @@ -48,12 +54,12 @@ func New( } state := State{ - dbs: bucket.NewBucket[k8s.RedisDB](), - clients: bucket.NewBucket[k8s.RedisClient](), - statefulSets: bucket.NewBucket[k8s.RedisStatefulSet](), - pvcs: bucket.NewBucket[k8s.RedisPVC](), - services: bucket.NewBucket[k8s.RedisService](), - secrets: bucket.NewBucket[k8s.RedisSecret](), + clusters: bucket.NewBucket[clusters.Resource](), + clients: bucket.NewBucket[clients.Resource](), + statefulSets: bucket.NewBucket[stateful_sets.Resource](), + pvcs: bucket.NewBucket[pvcs.Resource](), + services: bucket.NewBucket[services.Resource](), + secrets: bucket.NewBucket[secrets.Resource](), } return &Manager{ @@ -135,9 +141,9 @@ func (m *Manager) processRedisDBs() { err := m.client.StatefulSets().Create(m.ctx, db.Target) if err != nil { log.Error().Err(err).Msg("Failed to create redis stateful set") - m.client.DBs().Event(m.ctx, db.Parent, "Normal", "ProvisioningFailed", fmt.Sprintf("Failed to create stateful set: %s", err.Error())) + m.client.Clusters().Event(m.ctx, db.Parent, "Normal", "ProvisioningFailed", fmt.Sprintf("Failed to create stateful set: %s", err.Error())) } else { - m.client.DBs().Event(m.ctx, db.Parent, "Normal", "ProvisioningSucceeded", "Created stateful set") + m.client.Clusters().Event(m.ctx, db.Parent, "Normal", "ProvisioningSucceeded", "Created stateful set") } } diff --git a/internal/dbs/redis/manager/state.go b/internal/dbs/redis/manager/state.go index e36bf34..ba7e0c5 100644 --- a/internal/dbs/redis/manager/state.go +++ b/internal/dbs/redis/manager/state.go @@ -1,7 +1,12 @@ package manager import ( - "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/pvcs" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/services" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -9,85 +14,88 @@ import ( ) type State struct { - dbs bucket.Bucket[k8s.RedisDB, *k8s.RedisDB] - clients bucket.Bucket[k8s.RedisClient, *k8s.RedisClient] - statefulSets bucket.Bucket[k8s.RedisStatefulSet, *k8s.RedisStatefulSet] - pvcs bucket.Bucket[k8s.RedisPVC, *k8s.RedisPVC] - services bucket.Bucket[k8s.RedisService, *k8s.RedisService] - secrets bucket.Bucket[k8s.RedisSecret, *k8s.RedisSecret] + clusters bucket.Bucket[clusters.Resource] + clients bucket.Bucket[clients.Resource] + statefulSets bucket.Bucket[stateful_sets.Resource] + pvcs bucket.Bucket[pvcs.Resource] + services bucket.Bucket[services.Resource] + secrets bucket.Bucket[secrets.Resource] } func (s *State) Apply(update interface{}) { switch u := update.(type) { - case k8s_generic.Update[k8s.RedisDB]: - s.dbs.Apply(u) - case k8s_generic.Update[k8s.RedisClient]: + case k8s_generic.Update[clusters.Resource]: + s.clusters.Apply(u) + case k8s_generic.Update[clients.Resource]: s.clients.Apply(u) - case k8s_generic.Update[k8s.RedisStatefulSet]: + case k8s_generic.Update[stateful_sets.Resource]: s.statefulSets.Apply(u) - case k8s_generic.Update[k8s.RedisPVC]: + case k8s_generic.Update[pvcs.Resource]: s.pvcs.Apply(u) - case k8s_generic.Update[k8s.RedisService]: + case k8s_generic.Update[services.Resource]: s.services.Apply(u) - case k8s_generic.Update[k8s.RedisSecret]: + case k8s_generic.Update[secrets.Resource]: s.secrets.Apply(u) default: log.Error().Interface("update", u).Msg("wat dis? Unknown state update") } } -func (s *State) GetStatefulSetDemand() state.Demand[k8s.RedisDB, k8s.RedisStatefulSet] { +func (s *State) GetStatefulSetDemand() state.Demand[clusters.Resource, stateful_sets.Resource] { return state.GetOneForOne( - s.dbs, + s.clusters, s.statefulSets, - func(db k8s.RedisDB) k8s.RedisStatefulSet { - return k8s.RedisStatefulSet{ - RedisStatefulSetComparable: k8s.RedisStatefulSetComparable{ - Name: db.Name, - Namespace: db.Namespace, - Storage: db.Storage, + func(c clusters.Resource) stateful_sets.Resource { + return stateful_sets.Resource{ + Comparable: stateful_sets.Comparable{ + Name: c.Name, + Namespace: c.Namespace, + Storage: c.Storage, }, } }, ) } -func (s *State) GetServiceDemand() state.Demand[k8s.RedisDB, k8s.RedisService] { +func (s *State) GetServiceDemand() state.Demand[clusters.Resource, services.Resource] { return state.GetOneForOne( - s.dbs, + s.clusters, s.services, - func(db k8s.RedisDB) k8s.RedisService { - return k8s.RedisService{ - RedisServiceComparable: k8s.RedisServiceComparable{ - Name: db.Name, - Namespace: db.Namespace, + func(c clusters.Resource) services.Resource { + return services.Resource{ + Comparable: services.Comparable{ + Name: c.Name, + Namespace: c.Namespace, }, } }, ) } -func (s *State) GetPVCDemand() []k8s.RedisPVC { +func (s *State) GetPVCDemand() []pvcs.Resource { return state.GetOrphaned( s.statefulSets, s.pvcs, - func(ss k8s.RedisStatefulSet, pvc k8s.RedisPVC) bool { + func(ss stateful_sets.Resource, pvc pvcs.Resource) bool { return ss.Name == pvc.Database }, ) } -func (s *State) GetSecretsDemand() state.Demand[k8s.RedisClient, k8s.RedisSecret] { +func (s *State) GetSecretsDemand() state.Demand[clients.Resource, secrets.Resource] { return state.GetServiceBound( s.clients, s.secrets, s.statefulSets, - func(client k8s.RedisClient) k8s.RedisSecret { - return k8s.RedisSecret{ - RedisSecretComparable: k8s.RedisSecretComparable{ + func(client clients.Resource) secrets.Resource { + return secrets.Resource{ + Comparable: secrets.Comparable{ Name: client.Secret, Namespace: client.Namespace, - DB: client.DBRef, + Cluster: secrets.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, + }, }, } }, diff --git a/internal/state/bucket/bucket.go b/internal/state/bucket/bucket.go index 81f9dba..ab0a5ea 100644 --- a/internal/state/bucket/bucket.go +++ b/internal/state/bucket/bucket.go @@ -6,17 +6,17 @@ import ( "github.com/rs/zerolog/log" ) -type Bucket[T any, PT types.Nameable[T]] struct { +type Bucket[T types.Nameable] struct { state map[string]T } -func NewBucket[T any, PT types.Nameable[T]]() Bucket[T, PT] { - return Bucket[T, PT]{ +func NewBucket[T types.Nameable]() Bucket[T] { + return Bucket[T]{ state: map[string]T{}, } } -func (b *Bucket[T, PT]) Apply(update k8s_generic.Update[T]) { +func (b *Bucket[T]) Apply(update k8s_generic.Update[T]) { for _, toRemove := range update.ToRemove { log.Info().Interface("toRemove", toRemove).Msg("Removing") b.Remove(toRemove) @@ -28,30 +28,28 @@ func (b *Bucket[T, PT]) Apply(update k8s_generic.Update[T]) { } } -func (b *Bucket[T, PT]) Add(obj T) { - ptr := PT(&obj) - key := ptr.GetName() + ":" + ptr.GetNamespace() +func (b *Bucket[T]) Add(obj T) { + key := obj.GetName() + ":" + obj.GetNamespace() b.state[key] = obj } -func (b *Bucket[T, PT]) Remove(obj T) { - ptr := PT(&obj) - key := ptr.GetName() + ":" + ptr.GetNamespace() +func (b *Bucket[T]) Remove(obj T) { + key := obj.GetName() + ":" + obj.GetNamespace() delete(b.state, key) } -func (b *Bucket[T, PT]) Get(name string, namespace string) (T, bool) { +func (b *Bucket[T]) Get(name string, namespace string) (T, bool) { value, ok := b.state[name+":"+namespace] return value, ok } -func (b *Bucket[T, PT]) Clear() { +func (b *Bucket[T]) Clear() { b.state = map[string]T{} } -func (b *Bucket[T, PT]) List() []T { +func (b *Bucket[T]) List() []T { result := []T{} for _, v := range b.state { diff --git a/internal/state/demand.go b/internal/state/demand.go index 0e02636..678117f 100644 --- a/internal/state/demand.go +++ b/internal/state/demand.go @@ -16,24 +16,20 @@ type Demand[T any, U any] struct { } func GetOneForOne[ - T any, - U any, - PT types.Nameable[T], - PU types.Nameable[U], -](request bucket.Bucket[T, PT], existing bucket.Bucket[U, PU], transform func(T) U) Demand[T, U] { + T types.Nameable, + U types.Nameable, +](request bucket.Bucket[T], existing bucket.Bucket[U], transform func(T) U) Demand[T, U] { toAdd := []DemandTarget[T, U]{} toRemove := []DemandTarget[T, U]{} for _, obj := range request.List() { - ptr := PT(&obj) - if _, ok := existing.Get(ptr.GetName(), ptr.GetNamespace()); !ok { + if _, ok := existing.Get(obj.GetName(), obj.GetNamespace()); !ok { toAdd = append(toAdd, DemandTarget[T, U]{Parent: obj, Target: transform(obj)}) } } for _, obj := range existing.List() { - ptr := PU(&obj) - if _, ok := request.Get(ptr.GetName(), ptr.GetNamespace()); !ok { + if _, ok := request.Get(obj.GetName(), obj.GetNamespace()); !ok { toRemove = append(toRemove, DemandTarget[T, U]{Target: obj}) } } @@ -45,11 +41,9 @@ func GetOneForOne[ } func GetOrphaned[ - T any, - U any, - PT types.Nameable[T], - PU types.Nameable[U], -](current bucket.Bucket[T, PT], existing bucket.Bucket[U, PU], equals func(T, U) bool) []U { + T types.Nameable, + U types.Nameable, +](current bucket.Bucket[T], existing bucket.Bucket[U], equals func(T, U) bool) []U { toRemove := []U{} for _, obj := range existing.List() { @@ -71,27 +65,21 @@ func GetOrphaned[ } func GetStorageBound[ - T any, - U any, - PT types.HasStorage[T], - PU types.HasStorage[U], + T types.HasStorage, + U types.HasStorage, ]( - current bucket.Bucket[T, PT], - existing bucket.Bucket[U, PU], + current bucket.Bucket[T], + existing bucket.Bucket[U], transform func(T) U, ) Demand[T, U] { toAdd := []DemandTarget[T, U]{} toRemove := []DemandTarget[T, U]{} for _, db := range current.List() { - ptr := PT(&db) - if ss, ok := existing.Get(ptr.GetName(), ptr.GetNamespace()); !ok { + if ss, ok := existing.Get(db.GetName(), db.GetNamespace()); !ok { toAdd = append(toAdd, DemandTarget[T, U]{Parent: db, Target: transform(db)}) } else { - dbPtr := PT(&db) - ssPtr := PU(&ss) - - if dbPtr.GetStorage() != ssPtr.GetStorage() { + if db.GetStorage() != ss.GetStorage() { toRemove = append(toRemove, DemandTarget[T, U]{Parent: db, Target: transform(db)}) toAdd = append(toAdd, DemandTarget[T, U]{Parent: db, Target: transform(db)}) } @@ -99,8 +87,7 @@ func GetStorageBound[ } for _, db := range existing.List() { - ptr := PU(&db) - if _, ok := current.Get(ptr.GetName(), ptr.GetNamespace()); !ok { + if _, ok := current.Get(db.GetName(), db.GetNamespace()); !ok { toRemove = append(toRemove, DemandTarget[T, U]{Target: db}) } } @@ -111,10 +98,10 @@ func GetStorageBound[ } } -func GetServiceBound[T any, U any, V any, PT types.Targetable[T], PU types.Nameable[U], PV types.Readyable[V]]( - current bucket.Bucket[T, PT], - existing bucket.Bucket[U, PU], - servers bucket.Bucket[V, PV], +func GetServiceBound[T types.Targetable, U types.Nameable, V types.Readyable]( + current bucket.Bucket[T], + existing bucket.Bucket[U], + servers bucket.Bucket[V], transform func(T) U, ) Demand[T, U] { d := Demand[T, U]{ @@ -122,30 +109,25 @@ func GetServiceBound[T any, U any, V any, PT types.Targetable[T], PU types.Namea ToRemove: []DemandTarget[T, U]{}, } - seen := bucket.NewBucket[U, PU]() + seen := bucket.NewBucket[U]() for _, client := range current.List() { - clientPtr := PT(&client) - - ss, hasSS := servers.Get(clientPtr.GetTarget(), clientPtr.GetTargetNamespace()) - ssPtr := PV(&ss) + ss, hasSS := servers.Get(client.GetTarget(), client.GetTargetNamespace()) - if !hasSS || !ssPtr.IsReady() { + if !hasSS || !ss.IsReady() { continue } desired := transform(client) seen.Add(desired) - desiredPtr := PU(&desired) - if _, ok := existing.Get(desiredPtr.GetName(), desiredPtr.GetNamespace()); !ok { + if _, ok := existing.Get(desired.GetName(), desired.GetNamespace()); !ok { d.ToAdd = append(d.ToAdd, DemandTarget[T, U]{Parent: client, Target: desired}) } } for _, db := range existing.List() { - ptr := PU(&db) - if _, ok := seen.Get(ptr.GetName(), ptr.GetNamespace()); !ok { + if _, ok := seen.Get(db.GetName(), db.GetNamespace()); !ok { d.ToRemove = append(d.ToRemove, DemandTarget[T, U]{Target: db}) } } diff --git a/internal/state/types/interfaces.go b/internal/state/types/interfaces.go index f17422d..5212037 100644 --- a/internal/state/types/interfaces.go +++ b/internal/state/types/interfaces.go @@ -1,23 +1,22 @@ package types -type Nameable[T any] interface { - *T +type Nameable interface { GetName() string GetNamespace() string } -type HasStorage[T any] interface { - Nameable[T] +type HasStorage interface { + Nameable GetStorage() string } -type Readyable[T any] interface { - Nameable[T] +type Readyable interface { + Nameable IsReady() bool } -type Targetable[T any] interface { - Nameable[T] +type Targetable interface { + Nameable GetTarget() string GetTargetNamespace() string } diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 6ac7066..796f9b9 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -63,10 +63,16 @@ func (d *AdminConn) ListUsers() ([]string, error) { return users, nil } -func (d *AdminConn) CreateUser(username string) error { +func (d *AdminConn) CreateUser(username string, password string) error { log.Info().Msgf("Creating user %s", username) - if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)); err != nil { - return fmt.Errorf("failed to create database user: %+v", err) + if password != "" { + if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)+" WITH PASSWORD $1", password); err != nil { + return fmt.Errorf("failed to create database user: %+v", err) + } + } else { + if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)); err != nil { + return fmt.Errorf("failed to create database user: %+v", err) + } } return nil diff --git a/pkg/postgres/config.go b/pkg/postgres/config.go index 4558413..5d153f5 100644 --- a/pkg/postgres/config.go +++ b/pkg/postgres/config.go @@ -11,6 +11,7 @@ type ConnectConfig struct { Host string Port int Username string + Password string Database string } @@ -37,6 +38,8 @@ func ConfigFromEnv() (ConnectConfig, error) { return empty, errors.New("failed to lookup POSTGRES_USER env var") } + password, _ := os.LookupEnv("POSTGRES_PASS") + database, ok := os.LookupEnv("POSTGRES_NAME") if !ok { database = "default" @@ -46,6 +49,7 @@ func ConfigFromEnv() (ConnectConfig, error) { Host: host, Port: port, Username: user, + Password: password, Database: database, }, nil } @@ -73,9 +77,40 @@ func AdminFromEnv() (ConnectConfig, error) { return empty, errors.New("failed to lookup POSTGRES_ADMIN_USER env var") } + password, _ := os.LookupEnv("POSTGRES_ADMIN_PASS") + return ConnectConfig{ Host: host, Port: port, Username: user, + Password: password, }, nil } + +func (c ConnectConfig) ConnectionString() string { + dbSuffix := "" + if c.Database != "" { + dbSuffix = "/" + c.Database + } + + password := "" + if c.Password != "" { + password = ":" + c.Password + } + + return fmt.Sprintf("postgresql://%s%s@%s:%d%s", c.Username, password, c.Host, c.Port, dbSuffix) +} + +func (c ConnectConfig) String() string { + dbSuffix := "" + if c.Database != "" { + dbSuffix = "/" + c.Database + } + + password := "" + if c.Password != "" { + password = ":**********" + } + + return fmt.Sprintf("postgresql://%s%s@%s:%d%s", c.Username, password, c.Host, c.Port, dbSuffix) +} diff --git a/pkg/postgres/connect.go b/pkg/postgres/connect.go index 5936eed..81d556c 100644 --- a/pkg/postgres/connect.go +++ b/pkg/postgres/connect.go @@ -3,7 +3,6 @@ package postgres import ( "context" "errors" - "fmt" "time" "github.com/jackc/pgx/v4" @@ -42,14 +41,9 @@ func getConnection(config *pgx.ConnConfig) *pgx.Conn { } func Connect(config ConnectConfig) (*pgx.Conn, error) { - dbSuffix := "" - if config.Database != "" { - dbSuffix = "/" + config.Database - } - - connectionString := fmt.Sprintf("postgresql://%s@%s:%d%s", config.Username, config.Host, config.Port, dbSuffix) + connectionString := config.ConnectionString() - log.Info().Msgf("Connecting to postgres with connection string: %s", connectionString) + log.Info().Msgf("Connecting to postgres with connection string: %s", config) pgxConfig, err := pgx.ParseConfig(connectionString) if err != nil { diff --git a/tests/nats_test.go b/tests/nats_test.go index aba2081..7ced454 100644 --- a/tests/nats_test.go +++ b/tests/nats_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clusters" "github.com/stretchr/testify/assert" ) @@ -21,21 +23,21 @@ func TestNatsIntegration(t *testing.T) { t.FailNow() } - mustPass(t, client.DBs().DeleteAll(context.Background(), namespace)) + mustPass(t, client.Clusters().DeleteAll(context.Background(), namespace)) mustPass(t, client.Clients().DeleteAll(context.Background(), namespace)) - mustPass(t, client.DBs().Create(context.Background(), k8s.NatsDB{ - NatsDBComparable: k8s.NatsDBComparable{ + mustPass(t, client.Clusters().Create(context.Background(), clusters.Resource{ + Comparable: clusters.Comparable{ Name: "nats-db", Namespace: namespace, }, })) - mustPass(t, client.Clients().Create(context.Background(), k8s.NatsClient{ - NatsClientComparable: k8s.NatsClientComparable{ + mustPass(t, client.Clients().Create(context.Background(), clients.Resource{ + Comparable: clients.Comparable{ Name: "my-secret", Namespace: namespace, - DBRef: k8s.DBRef{ + Cluster: clients.Cluster{ Name: "nats-db", Namespace: namespace, }, diff --git a/tests/cockroach_test.go b/tests/postgres_test.go similarity index 57% rename from tests/cockroach_test.go rename to tests/postgres_test.go index a67e984..d537f30 100644 --- a/tests/cockroach_test.go +++ b/tests/postgres_test.go @@ -7,14 +7,17 @@ import ( "strconv" "testing" - "github.com/benjamin-wright/db-operator/internal/dbs/cockroach/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clusters" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" postgres_helpers "github.com/benjamin-wright/db-operator/internal/test_utils/postgres" "github.com/benjamin-wright/db-operator/pkg/postgres" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) -func TestCockroachIntegration(t *testing.T) { +func TestPostgresIntegration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } @@ -32,9 +35,8 @@ func TestCockroachIntegration(t *testing.T) { namespace := os.Getenv("NAMESPACE") - mustPass(t, client.DBs().DeleteAll(context.Background(), namespace)) + mustPass(t, client.Clusters().DeleteAll(context.Background(), namespace)) mustPass(t, client.Clients().DeleteAll(context.Background(), namespace)) - mustPass(t, client.Migrations().DeleteAll(context.Background(), namespace)) mustPass(t, waitFor(func() error { sss, err := client.StatefulSets().GetAll(context.Background()) if err != nil { @@ -48,17 +50,17 @@ func TestCockroachIntegration(t *testing.T) { return nil })) - mustPass(t, client.DBs().Create(context.Background(), k8s.CockroachDB{ - CockroachDBComparable: k8s.CockroachDBComparable{ + mustPass(t, client.Clusters().Create(context.Background(), clusters.Resource{ + Comparable: clusters.Comparable{ Name: "different-db", Namespace: namespace, Storage: "256Mi", }, })) - mustPass(t, client.Clients().Create(context.Background(), k8s.CockroachClient{ - CockroachClientComparable: k8s.CockroachClientComparable{ - DBRef: k8s.DBRef{Name: "different-db", Namespace: namespace}, + mustPass(t, client.Clients().Create(context.Background(), clients.Resource{ + Comparable: clients.Comparable{ + Cluster: clients.Cluster{Name: "different-db", Namespace: namespace}, Database: "new_db", Name: "my-client", Namespace: namespace, @@ -67,22 +69,7 @@ func TestCockroachIntegration(t *testing.T) { }, })) - mustPass(t, client.Migrations().Create(context.Background(), k8s.CockroachMigration{ - CockroachMigrationComparable: k8s.CockroachMigrationComparable{ - Name: "mig1", - Namespace: namespace, - DBRef: k8s.DBRef{Name: "different-db", Namespace: namespace}, - Database: "new_db", - Migration: ` - CREATE TABLE hithere ( - id INT PRIMARY KEY NOT NULL UNIQUE - ); - `, - Index: 1, - }, - })) - - secret := waitForResult(t, func() (k8s.CockroachSecret, error) { + secret := waitForResult(t, func() (secrets.Resource, error) { return client.Secrets().Get(context.Background(), "my-secret", namespace) }) @@ -99,9 +86,6 @@ func TestCockroachIntegration(t *testing.T) { tables := pg.GetTableNames(t) - expected := []string{ - "migrations", - "hithere", - } + expected := []string{} assert.Equal(t, expected, tables) } diff --git a/tests/redis_test.go b/tests/redis_test.go index 2e782c1..6490877 100644 --- a/tests/redis_test.go +++ b/tests/redis_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clusters" "github.com/stretchr/testify/assert" ) @@ -21,22 +23,22 @@ func TestRedisIntegration(t *testing.T) { t.FailNow() } - mustPass(t, client.DBs().DeleteAll(context.Background(), namespace)) + mustPass(t, client.Clusters().DeleteAll(context.Background(), namespace)) mustPass(t, client.Clients().DeleteAll(context.Background(), namespace)) - mustPass(t, client.DBs().Create(context.Background(), k8s.RedisDB{ - RedisDBComparable: k8s.RedisDBComparable{ + mustPass(t, client.Clusters().Create(context.Background(), clusters.Resource{ + Comparable: clusters.Comparable{ Name: "redis-db", Namespace: namespace, Storage: "256Mi", }, })) - mustPass(t, client.Clients().Create(context.Background(), k8s.RedisClient{ - RedisClientComparable: k8s.RedisClientComparable{ + mustPass(t, client.Clients().Create(context.Background(), clients.Resource{ + Comparable: clients.Comparable{ Name: "my-secret", Namespace: namespace, - DBRef: k8s.DBRef{ + Cluster: clients.Cluster{ Name: "redis-db", Namespace: namespace, }, From 1d1f1530af33230ec965e62d31f80321612dbff2 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sat, 18 May 2024 10:59:08 +0100 Subject: [PATCH 05/22] a bit more renaming, some updates and WIP setting randomised user passwords --- README.md | 2 +- go.mod | 77 +-- go.sum | 513 ++++-------------- internal/dbs/nats/manager/manager.go | 2 +- internal/dbs/postgres/database/client.go | 2 +- internal/dbs/postgres/database/migrations.go | 2 +- internal/dbs/postgres/k8s/pvcs/pvcs.go | 4 +- internal/dbs/postgres/k8s/pvcs/pvcs_test.go | 16 +- .../dbs/postgres/k8s/secrets/secrets_test.go | 2 +- .../dbs/postgres/k8s/services/services.go | 4 +- .../dbs/postgres/managers/database/manager.go | 60 +- .../managers/database/manager_test.go | 66 +++ internal/dbs/redis/manager/manager.go | 2 +- internal/state/demand.go | 14 +- internal/utils/passwords.go | 34 ++ .../cockroach.go => postgres/postgres.go} | 8 +- tilt/db_operator/Tiltfile | 4 +- 17 files changed, 338 insertions(+), 474 deletions(-) create mode 100644 internal/dbs/postgres/managers/database/manager_test.go create mode 100644 internal/utils/passwords.go rename pkg/test/{cockroach/cockroach.go => postgres/postgres.go} (88%) diff --git a/README.md b/README.md index 9796553..b881e8b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,6 @@ An operator for creating and managing development databases and other stateful infrastructure Currently supports: -- CockroachDB +- Postgres - Redis - NATs \ No newline at end of file diff --git a/go.mod b/go.mod index 2891003..9a730c1 100644 --- a/go.mod +++ b/go.mod @@ -1,64 +1,67 @@ module github.com/benjamin-wright/db-operator -go 1.19 +go 1.22.0 + +toolchain go1.22.1 require ( github.com/go-redis/redis/v8 v8.11.5 - github.com/jackc/pgconn v1.14.0 - github.com/jackc/pgx/v4 v4.18.1 - github.com/rs/zerolog v1.15.0 - github.com/stretchr/testify v1.8.1 - k8s.io/api v0.27.2 - k8s.io/apimachinery v0.27.2 - k8s.io/client-go v0.27.2 + github.com/jackc/pgconn v1.14.3 + github.com/jackc/pgx/v4 v4.18.3 + github.com/rs/zerolog v1.32.0 + github.com/stretchr/testify v1.9.0 + k8s.io/api v0.30.1 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.1 ) require ( - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgtype v1.14.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.90.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f // indirect + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index c8c585d..8d525b8 100644 --- a/go.sum +++ b/go.sum @@ -1,154 +1,62 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -159,8 +67,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -176,23 +84,26 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= +github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -201,16 +112,13 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -225,9 +133,15 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -236,20 +150,27 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= @@ -258,7 +179,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -271,19 +191,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -298,7 +212,6 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -306,291 +219,106 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -598,39 +326,30 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= -k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= -k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= -k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= -k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGcXawNMouPECM1+F9BVxEaM= +k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/dbs/nats/manager/manager.go b/internal/dbs/nats/manager/manager.go index a59c39b..168235d 100644 --- a/internal/dbs/nats/manager/manager.go +++ b/internal/dbs/nats/manager/manager.go @@ -32,7 +32,7 @@ func New( ) (*Manager, error) { client, err := k8s.New() if err != nil { - return nil, fmt.Errorf("failed to create cockroach client: %+v", err) + return nil, fmt.Errorf("failed to create postgres client: %+v", err) } ctx, cancel := context.WithCancel(context.Background()) diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index 32d5888..00c076c 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -22,7 +22,7 @@ func New(database string, namespace string) (*Client, error) { conn, err := postgres.NewAdminConn(cfg) if err != nil { - return nil, fmt.Errorf("failed to connect to cockroach db at %s: %+v", database, err) + return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", database, err) } return &Client{ diff --git a/internal/dbs/postgres/database/migrations.go b/internal/dbs/postgres/database/migrations.go index 3913c7e..4696847 100644 --- a/internal/dbs/postgres/database/migrations.go +++ b/internal/dbs/postgres/database/migrations.go @@ -26,7 +26,7 @@ func NewMigrations(deployment string, namespace string, database string) (*Migra conn, err := postgres.Connect(cfg) if err != nil { - return nil, fmt.Errorf("failed to connect to cockroach db at %s: %+v", database, err) + return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", database, err) } return &MigrationsClient{ diff --git a/internal/dbs/postgres/k8s/pvcs/pvcs.go b/internal/dbs/postgres/k8s/pvcs/pvcs.go index fa9146f..1f53e42 100644 --- a/internal/dbs/postgres/k8s/pvcs/pvcs.go +++ b/internal/dbs/postgres/k8s/pvcs/pvcs.go @@ -78,9 +78,9 @@ func (r Resource) GetResourceVersion() string { } func (r Resource) Equal(obj k8s_generic.Resource) bool { - cockroachPVC, ok := obj.(*Resource) + other, ok := obj.(*Resource) if !ok { return false } - return r.Comparable == cockroachPVC.Comparable + return r.Comparable == other.Comparable } diff --git a/internal/dbs/postgres/k8s/pvcs/pvcs_test.go b/internal/dbs/postgres/k8s/pvcs/pvcs_test.go index 33caa15..3533ca5 100644 --- a/internal/dbs/postgres/k8s/pvcs/pvcs_test.go +++ b/internal/dbs/postgres/k8s/pvcs/pvcs_test.go @@ -7,7 +7,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func TestCockroachPVCFromUnstructured(t *testing.T) { +func TestPostgresPVCFromUnstructured(t *testing.T) { pvc := &unstructured.Unstructured{ Object: map[string]interface{}{ "metadata": map[string]interface{}{ @@ -30,13 +30,13 @@ func TestCockroachPVCFromUnstructured(t *testing.T) { }, } - cockroachPVC, err := fromUnstructured(pvc) + postgresPVC, err := fromUnstructured(pvc) assert.NoError(t, err) - assert.Equal(t, "test-name", cockroachPVC.Name) - assert.Equal(t, "test-namespace", cockroachPVC.Namespace) - assert.Equal(t, "test-uid", cockroachPVC.UID) - assert.Equal(t, "test-resource-version", cockroachPVC.ResourceVersion) - assert.Equal(t, "test-storage", cockroachPVC.Storage) - assert.Equal(t, "test-cluster", cockroachPVC.Cluster) + assert.Equal(t, "test-name", postgresPVC.Name) + assert.Equal(t, "test-namespace", postgresPVC.Namespace) + assert.Equal(t, "test-uid", postgresPVC.UID) + assert.Equal(t, "test-resource-version", postgresPVC.ResourceVersion) + assert.Equal(t, "test-storage", postgresPVC.Storage) + assert.Equal(t, "test-cluster", postgresPVC.Cluster) } diff --git a/internal/dbs/postgres/k8s/secrets/secrets_test.go b/internal/dbs/postgres/k8s/secrets/secrets_test.go index f4e1919..9904d48 100644 --- a/internal/dbs/postgres/k8s/secrets/secrets_test.go +++ b/internal/dbs/postgres/k8s/secrets/secrets_test.go @@ -21,7 +21,7 @@ func decode(t *testing.T, data interface{}) string { return string(decoded) } -func TestCockroachSecretFromUnstructured(t *testing.T) { +func TestPostgresSecretFromUnstructured(t *testing.T) { secret := &Resource{ Comparable: Comparable{ Name: "test-name", diff --git a/internal/dbs/postgres/k8s/services/services.go b/internal/dbs/postgres/k8s/services/services.go index 2b60269..3a238c7 100644 --- a/internal/dbs/postgres/k8s/services/services.go +++ b/internal/dbs/postgres/k8s/services/services.go @@ -91,9 +91,9 @@ func (r Resource) GetResourceVersion() string { } func (r Resource) Equal(obj k8s_generic.Resource) bool { - cockroachService, ok := obj.(*Resource) + other, ok := obj.(*Resource) if !ok { return false } - return r.Comparable == cockroachService.Comparable + return r.Comparable == other.Comparable } diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index e8ca7ca..f27c805 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -10,6 +10,7 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" + "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/internal/utils" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -97,7 +98,7 @@ func (m *Manager) refresh() { log.Info().Msg("Updating postgres database state") m.refreshDatabaseState() log.Info().Msg("Processing postgres databases started") - m.processCockroachClients() + m.processPostgresClients() log.Info().Msg("Processing postgres databases finished") } } @@ -167,12 +168,60 @@ func (m *Manager) refreshDatabaseState() { } } -func (m *Manager) processCockroachClients() { +func isUserSecret(user database.User, secret secrets.Resource) bool { + return user.Cluster.Name == secret.Cluster.Name && user.Cluster.Namespace == secret.Cluster.Namespace && user.Name == secret.User +} + +func setPasswords( + secretsDemand *state.Demand[clients.Resource, secrets.Resource], + userDemand *state.Demand[clients.Resource, database.User], +) { + secretIds := []int{} + userIds := []int{} + for secretId, secret := range secretsDemand.ToAdd { + missing := true + for userId, user := range userDemand.ToAdd { + if isUserSecret(user.Target, secret.Target) { + password, err := utils.GeneratePassword(32, true, true) + if err != nil { + // remove the secret and user from the ToAdd list + secretIds = append(secretIds, secretId) + userIds = append(userIds, userId) + log.Error().Err(err).Msgf("Failed to generate password for user %s in db %s", user.Target.Name, user.Target.Cluster) + continue + } + + userDemand.ToAdd[userId].Target.Password = password + secretsDemand.ToAdd[secretId].Target.Password = password + missing = false + break + } + } + + if missing { + // remove the secret from the ToAdd list + secretIds = append(secretIds, secretId) + log.Error().Msgf("Failed to find user for secret %s", secret.Target.Name) + } + } + + for _, secretId := range secretIds { + secretsDemand.ToAdd = append(secretsDemand.ToAdd[:secretId], secretsDemand.ToAdd[secretId+1:]...) + } + + for _, userId := range userIds { + userDemand.ToAdd = append(userDemand.ToAdd[:userId], userDemand.ToAdd[userId+1:]...) + } +} + +func (m *Manager) processPostgresClients() { dbDemand := m.state.GetDBDemand() userDemand := m.state.GetUserDemand() permsDemand := m.state.GetPermissionDemand() secretsDemand := m.state.GetSecretsDemand() + setPasswords(&secretsDemand, &userDemand) + dbs := newConsolidator() for _, db := range dbDemand.ToAdd { dbs.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) @@ -286,13 +335,6 @@ func (m *Manager) processCockroachClients() { } for _, secret := range secretsDemand.ToAdd { - user, ok := m.state.users.Get(secret.Target.User, secret.Target.Namespace) - if !ok { - log.Error().Msgf("Failed to find user %s in namespace %s for secret %s", secret.Target.User, secret.Target.Namespace, secret.Target.Name) - continue - } - secret.Target.Password = user.Password - log.Info().Msgf("Adding secret %s", secret.Target.Name) err := m.client.Secrets().Create(m.ctx, secret.Target) if err != nil { diff --git a/internal/dbs/postgres/managers/database/manager_test.go b/internal/dbs/postgres/managers/database/manager_test.go new file mode 100644 index 0000000..13c47a9 --- /dev/null +++ b/internal/dbs/postgres/managers/database/manager_test.go @@ -0,0 +1,66 @@ +package database + +import ( + "testing" + + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/state" + "github.com/stretchr/testify/assert" +) + +func secret(user string, cluster string, namespace string) state.DemandTarget[clients.Resource, secrets.Resource] { + return state.DemandTarget[clients.Resource, secrets.Resource]{ + Parent: clients.Resource{}, + Target: secrets.Resource{ + Comparable: secrets.Comparable{ + User: user, + Cluster: secrets.Cluster{ + Name: cluster, + Namespace: namespace, + }, + }, + }, + } +} + +func user(name string, cluster string, namespace string) state.DemandTarget[clients.Resource, database.User] { + return state.DemandTarget[clients.Resource, database.User]{ + Parent: clients.Resource{}, + Target: database.User{ + Name: name, + Cluster: database.Cluster{ + Name: cluster, + Namespace: namespace, + }, + }, + } +} + +func TestSetPasswords(t *testing.T) { + secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ + ToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + secret("user1", "cluster1", "namespace1"), + }, + ToRemove: []state.DemandTarget[clients.Resource, secrets.Resource]{}, + } + + usersDemand := state.Demand[clients.Resource, database.User]{ + ToAdd: []state.DemandTarget[clients.Resource, database.User]{ + user("user1", "cluster1", "namespace1"), + }, + ToRemove: []state.DemandTarget[clients.Resource, database.User]{}, + } + + setPasswords(&secretsDemand, &usersDemand) + + assert.Len(t, secretsDemand.ToAdd, 1) + assert.Len(t, secretsDemand.ToRemove, 0) + assert.Len(t, usersDemand.ToAdd, 1) + assert.Len(t, usersDemand.ToRemove, 0) + + password := usersDemand.ToAdd[0].Target.Password + assert.NotEmpty(t, password) + assert.Equal(t, password, secretsDemand.ToAdd[0].Target.Password) +} diff --git a/internal/dbs/redis/manager/manager.go b/internal/dbs/redis/manager/manager.go index e3a7f71..c09fc5e 100644 --- a/internal/dbs/redis/manager/manager.go +++ b/internal/dbs/redis/manager/manager.go @@ -33,7 +33,7 @@ func New( ) (*Manager, error) { client, err := k8s.New() if err != nil { - return nil, fmt.Errorf("failed to create cockroach client: %+v", err) + return nil, fmt.Errorf("failed to create postgres client: %+v", err) } ctx, cancel := context.WithCancel(context.Background()) diff --git a/internal/state/demand.go b/internal/state/demand.go index 678117f..2ffab6f 100644 --- a/internal/state/demand.go +++ b/internal/state/demand.go @@ -43,13 +43,13 @@ func GetOneForOne[ func GetOrphaned[ T types.Nameable, U types.Nameable, -](current bucket.Bucket[T], existing bucket.Bucket[U], equals func(T, U) bool) []U { +](demand bucket.Bucket[T], existing bucket.Bucket[U], equals func(T, U) bool) []U { toRemove := []U{} for _, obj := range existing.List() { missing := true - for _, ref := range current.List() { + for _, ref := range demand.List() { if equals(ref, obj) { missing = false break @@ -68,14 +68,14 @@ func GetStorageBound[ T types.HasStorage, U types.HasStorage, ]( - current bucket.Bucket[T], + demand bucket.Bucket[T], existing bucket.Bucket[U], transform func(T) U, ) Demand[T, U] { toAdd := []DemandTarget[T, U]{} toRemove := []DemandTarget[T, U]{} - for _, db := range current.List() { + for _, db := range demand.List() { if ss, ok := existing.Get(db.GetName(), db.GetNamespace()); !ok { toAdd = append(toAdd, DemandTarget[T, U]{Parent: db, Target: transform(db)}) } else { @@ -87,7 +87,7 @@ func GetStorageBound[ } for _, db := range existing.List() { - if _, ok := current.Get(db.GetName(), db.GetNamespace()); !ok { + if _, ok := demand.Get(db.GetName(), db.GetNamespace()); !ok { toRemove = append(toRemove, DemandTarget[T, U]{Target: db}) } } @@ -99,7 +99,7 @@ func GetStorageBound[ } func GetServiceBound[T types.Targetable, U types.Nameable, V types.Readyable]( - current bucket.Bucket[T], + demand bucket.Bucket[T], existing bucket.Bucket[U], servers bucket.Bucket[V], transform func(T) U, @@ -111,7 +111,7 @@ func GetServiceBound[T types.Targetable, U types.Nameable, V types.Readyable]( seen := bucket.NewBucket[U]() - for _, client := range current.List() { + for _, client := range demand.List() { ss, hasSS := servers.Get(client.GetTarget(), client.GetTargetNamespace()) if !hasSS || !ss.IsReady() { diff --git a/internal/utils/passwords.go b/internal/utils/passwords.go new file mode 100644 index 0000000..2dbfdae --- /dev/null +++ b/internal/utils/passwords.go @@ -0,0 +1,34 @@ +package utils + +import ( + "crypto/rand" + "math/big" +) + +func GeneratePassword(length int, includeNumeric bool, includeSpecial bool) (string, error) { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + const numeric = "0123456789" + const special = "!@#$%^&*()_+=-" + + password := make([]byte, length) + var charSource string + + if includeNumeric { + charSource += numeric + } + if includeSpecial { + charSource += special + } + charSource += charset + + bigLength := big.NewInt(int64(len(charSource))) + + for i := 0; i < length; i++ { + randNum, err := rand.Int(rand.Reader, bigLength) + if err != nil { + panic(err) + } + password = append(password, charSource[randNum.Int64()]) + } + return string(password), nil +} diff --git a/pkg/test/cockroach/cockroach.go b/pkg/test/postgres/postgres.go similarity index 88% rename from pkg/test/cockroach/cockroach.go rename to pkg/test/postgres/postgres.go index efea2a2..fef0654 100644 --- a/pkg/test/cockroach/cockroach.go +++ b/pkg/test/postgres/postgres.go @@ -1,4 +1,4 @@ -package cockroach +package postgres import ( "context" @@ -21,7 +21,7 @@ func Run(name string, port int64) func() { // Try to remove any existing containers exec.Command("docker", "stop", name).Run() - image := "cockroachdb/cockroach:v22.2.8" + image := "postgres:16.3" args := "--logtostderr start-single-node --insecure --listen-addr 0.0.0.0:" + strconv.FormatInt(port, 10) cmdString := fmt.Sprintf( @@ -31,13 +31,13 @@ func Run(name string, port int64) func() { cmd := exec.Command("docker", strings.Split(cmdString, " ")...) if err := cmd.Run(); err != nil { - log.Fatal().Err(err).Msg("Failed to start cockroach container") + log.Fatal().Err(err).Msg("Failed to start postgres container") } return func() { cmd := exec.Command("docker", "stop", name) if err := cmd.Run(); err != nil { - log.Fatal().Err(err).Msg("Failed to stop cockroach container") + log.Fatal().Err(err).Msg("Failed to stop postgres container") } } } diff --git a/tilt/db_operator/Tiltfile b/tilt/db_operator/Tiltfile index eecbdfa..51f7bcd 100644 --- a/tilt/db_operator/Tiltfile +++ b/tilt/db_operator/Tiltfile @@ -2,8 +2,8 @@ load('ext://helm_resource', 'helm_resource') def db(namespace, name, db_type, storage): kinds = { - 'redis': 'RedisDB', - 'cockroach': 'CockroachDB', + 'redis': 'RedisCluster', + 'postgres': 'PostgresCluster', } k8s_yaml(blob(""" From ee646024c046bd42901977fe2f88eebaae90fefe Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sat, 18 May 2024 17:11:50 +0100 Subject: [PATCH 06/22] WIP progress --- .../dbs/postgres/managers/database/manager.go | 48 ++++++----- .../managers/database/manager_test.go | 80 ++++++++++++++----- internal/state/demand.go | 6 +- 3 files changed, 93 insertions(+), 41 deletions(-) diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index f27c805..481a810 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -175,34 +175,46 @@ func isUserSecret(user database.User, secret secrets.Resource) bool { func setPasswords( secretsDemand *state.Demand[clients.Resource, secrets.Resource], userDemand *state.Demand[clients.Resource, database.User], + users bucket.Bucket[database.User], ) { secretIds := []int{} userIds := []int{} for secretId, secret := range secretsDemand.ToAdd { - missing := true - for userId, user := range userDemand.ToAdd { - if isUserSecret(user.Target, secret.Target) { - password, err := utils.GeneratePassword(32, true, true) - if err != nil { - // remove the secret and user from the ToAdd list - secretIds = append(secretIds, secretId) - userIds = append(userIds, userId) - log.Error().Err(err).Msgf("Failed to generate password for user %s in db %s", user.Target.Name, user.Target.Cluster) - continue - } + userId := -1 - userDemand.ToAdd[userId].Target.Password = password - secretsDemand.ToAdd[secretId].Target.Password = password - missing = false + // check if the user already exists in the user demand + for id, user := range userDemand.ToAdd { + if isUserSecret(user.Target, secret.Target) { + userId = id break } } - if missing { - // remove the secret from the ToAdd list + // if the user does not exist in the user demand it must already exist, so delete the user and re-add it + if userId < 0 { + existing, _ := users.Get(secretsDemand.ToAdd[secretId].Target.User, secretsDemand.ToAdd[secretId].Target.Namespace) + + userDemand.ToRemove = append(userDemand.ToRemove, state.DemandTarget[clients.Resource, database.User]{ + Target: existing, + }) + userDemand.ToAdd = append(userDemand.ToAdd, state.DemandTarget[clients.Resource, database.User]{ + Parent: secret.Parent, + Target: existing, + }) + userId = len(userDemand.ToAdd) - 1 + } + + password, err := utils.GeneratePassword(32, true, true) + if err != nil { + // remove the secret and user from the ToAdd list secretIds = append(secretIds, secretId) - log.Error().Msgf("Failed to find user for secret %s", secret.Target.Name) + userIds = append(userIds, userId) + log.Error().Err(err).Msgf("Failed to generate password for user: %v", err) + continue } + + userDemand.ToAdd[userId].Target.Password = password + secretsDemand.ToAdd[secretId].Target.Password = password } for _, secretId := range secretIds { @@ -220,7 +232,7 @@ func (m *Manager) processPostgresClients() { permsDemand := m.state.GetPermissionDemand() secretsDemand := m.state.GetSecretsDemand() - setPasswords(&secretsDemand, &userDemand) + setPasswords(&secretsDemand, &userDemand, m.state.users) dbs := newConsolidator() for _, db := range dbDemand.ToAdd { diff --git a/internal/dbs/postgres/managers/database/manager_test.go b/internal/dbs/postgres/managers/database/manager_test.go index 13c47a9..36998ad 100644 --- a/internal/dbs/postgres/managers/database/manager_test.go +++ b/internal/dbs/postgres/managers/database/manager_test.go @@ -7,6 +7,7 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" "github.com/benjamin-wright/db-operator/internal/state" + "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/stretchr/testify/assert" ) @@ -39,28 +40,67 @@ func user(name string, cluster string, namespace string) state.DemandTarget[clie } func TestSetPasswords(t *testing.T) { - secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ - ToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ - secret("user1", "cluster1", "namespace1"), - }, - ToRemove: []state.DemandTarget[clients.Resource, secrets.Resource]{}, - } + t.Run("set passwords", func(t *testing.T) { + secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ + ToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + secret("user1", "cluster1", "namespace1"), + }, + ToRemove: []state.DemandTarget[clients.Resource, secrets.Resource]{}, + } - usersDemand := state.Demand[clients.Resource, database.User]{ - ToAdd: []state.DemandTarget[clients.Resource, database.User]{ - user("user1", "cluster1", "namespace1"), - }, - ToRemove: []state.DemandTarget[clients.Resource, database.User]{}, - } + usersDemand := state.Demand[clients.Resource, database.User]{ + ToAdd: []state.DemandTarget[clients.Resource, database.User]{ + user("user1", "cluster1", "namespace1"), + }, + ToRemove: []state.DemandTarget[clients.Resource, database.User]{}, + } + + setPasswords(&secretsDemand, &usersDemand, bucket.Bucket[database.User]{}) + + assert.Len(t, secretsDemand.ToAdd, 1) + assert.Len(t, secretsDemand.ToRemove, 0) + assert.Len(t, usersDemand.ToAdd, 1) + assert.Len(t, usersDemand.ToRemove, 0) + + password := usersDemand.ToAdd[0].Target.Password + assert.NotEmpty(t, password) + assert.Len(t, password, 32*2) + assert.Equal(t, password, secretsDemand.ToAdd[0].Target.Password) + }) + + t.Run("secret required but user already exists", func(t *testing.T) { + secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ + ToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + secret("user1", "cluster1", "namespace1"), + }, + ToRemove: []state.DemandTarget[clients.Resource, secrets.Resource]{}, + } + + usersDemand := state.Demand[clients.Resource, database.User]{ + ToAdd: []state.DemandTarget[clients.Resource, database.User]{}, + ToRemove: []state.DemandTarget[clients.Resource, database.User]{}, + } + + existingUsers := bucket.NewBucket[database.User]() + existingUsers.Add(database.User{ + Name: "user1", + Cluster: database.Cluster{ + Name: "cluster1", + Namespace: "namespace1", + }, + }) + + setPasswords(&secretsDemand, &usersDemand, existingUsers) - setPasswords(&secretsDemand, &usersDemand) + assert.Len(t, secretsDemand.ToAdd, 1) + assert.Len(t, secretsDemand.ToRemove, 0) + assert.Len(t, usersDemand.ToAdd, 1) + assert.Len(t, usersDemand.ToRemove, 1) - assert.Len(t, secretsDemand.ToAdd, 1) - assert.Len(t, secretsDemand.ToRemove, 0) - assert.Len(t, usersDemand.ToAdd, 1) - assert.Len(t, usersDemand.ToRemove, 0) + password := usersDemand.ToAdd[0].Target.Password - password := usersDemand.ToAdd[0].Target.Password - assert.NotEmpty(t, password) - assert.Equal(t, password, secretsDemand.ToAdd[0].Target.Password) + assert.NotEmpty(t, password) + assert.Len(t, password, 32*2) + assert.Equal(t, password, secretsDemand.ToAdd[0].Target.Password) + }) } diff --git a/internal/state/demand.go b/internal/state/demand.go index 2ffab6f..ae817c0 100644 --- a/internal/state/demand.go +++ b/internal/state/demand.go @@ -126,9 +126,9 @@ func GetServiceBound[T types.Targetable, U types.Nameable, V types.Readyable]( } } - for _, db := range existing.List() { - if _, ok := seen.Get(db.GetName(), db.GetNamespace()); !ok { - d.ToRemove = append(d.ToRemove, DemandTarget[T, U]{Target: db}) + for _, e := range existing.List() { + if _, ok := seen.Get(e.GetName(), e.GetNamespace()); !ok { + d.ToRemove = append(d.ToRemove, DemandTarget[T, U]{Target: e}) } } From 296b485ba1911da37baa7b10fa498a9f32167b87 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sun, 19 May 2024 16:48:27 +0100 Subject: [PATCH 07/22] mostly done with refactor, now need to convert queries from CockroachDB to postgres --- .envrc | 3 + cmd/operator/main.go | 1 + internal/dbs/nats/manager/manager.go | 24 +- internal/dbs/postgres/database/client.go | 5 +- internal/dbs/postgres/k8s/secrets/secrets.go | 4 + .../dbs/postgres/managers/database/manager.go | 135 +++------- .../managers/database/manager_test.go | 106 -------- .../postgres/managers/database/passwords.go | 123 +++++++++ .../managers/database/passwords_test.go | 227 ++++++++++++++++ .../dbs/postgres/managers/database/state.go | 245 ++++++++++++++---- .../postgres/managers/deployment/manager.go | 16 +- internal/dbs/redis/manager/manager.go | 24 +- internal/state/demand.go | 51 ++-- internal/utils/passwords.go | 4 +- pkg/postgres/admin.go | 4 +- 15 files changed, 654 insertions(+), 318 deletions(-) create mode 100644 .envrc delete mode 100644 internal/dbs/postgres/managers/database/manager_test.go create mode 100644 internal/dbs/postgres/managers/database/passwords.go create mode 100644 internal/dbs/postgres/managers/database/passwords_test.go diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..58d9e58 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +#!/bin/bash + +export KUBECONFIG=.scratch/kubeconfig \ No newline at end of file diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 757f7e3..98c10d9 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -25,6 +25,7 @@ func main() { } zerolog.SetGlobalLevel(level) + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Msg("Starting operator...") diff --git a/internal/dbs/nats/manager/manager.go b/internal/dbs/nats/manager/manager.go index 168235d..d1d6d65 100644 --- a/internal/dbs/nats/manager/manager.go +++ b/internal/dbs/nats/manager/manager.go @@ -105,25 +105,25 @@ func (m *Manager) processNatsDBs() { dDemand := m.state.GetDeploymentDemand() svcDemand := m.state.GetServiceDemand() - for _, db := range dDemand.ToRemove { - log.Info().Msgf("Deleting db: %s/%s", db.Target.Namespace, db.Target.Name) - err := m.client.Deployments().Delete(m.ctx, db.Target.Name, db.Target.Namespace) + for _, db := range dDemand.ToRemove.List() { + log.Info().Msgf("Deleting db: %s/%s", db.Namespace, db.Name) + err := m.client.Deployments().Delete(m.ctx, db.Name, db.Namespace) if err != nil { log.Error().Err(err).Msg("Failed to delete nats deployment") } } - for _, svc := range svcDemand.ToRemove { - log.Info().Msgf("Deleting service: %s/%s", svc.Target.Namespace, svc.Target.Name) - err := m.client.Services().Delete(m.ctx, svc.Target.Name, svc.Target.Namespace) + for _, svc := range svcDemand.ToRemove.List() { + log.Info().Msgf("Deleting service: %s/%s", svc.Namespace, svc.Name) + err := m.client.Services().Delete(m.ctx, svc.Name, svc.Namespace) if err != nil { log.Error().Err(err).Msg("Failed to delete nats service") } } - for _, db := range dDemand.ToAdd { + for _, db := range dDemand.ToAdd.List() { log.Info().Msgf("Creating db: %s/%s", db.Target.Namespace, db.Target.Name) err := m.client.Deployments().Create(m.ctx, db.Target) if err != nil { @@ -134,7 +134,7 @@ func (m *Manager) processNatsDBs() { } } - for _, svc := range svcDemand.ToAdd { + for _, svc := range svcDemand.ToAdd.List() { log.Info().Msgf("Creating service: %s/%s", svc.Target.Namespace, svc.Target.Name) err := m.client.Services().Create(m.ctx, svc.Target) @@ -147,16 +147,16 @@ func (m *Manager) processNatsDBs() { func (m *Manager) processNatsDeployments() { secretsDemand := m.state.GetSecretsDemand() - for _, secret := range secretsDemand.ToRemove { - log.Info().Msgf("Deleting secret: %s/%s", secret.Target.Namespace, secret.Target.Name) - err := m.client.Secrets().Delete(m.ctx, secret.Target.Name, secret.Target.Namespace) + for _, secret := range secretsDemand.ToRemove.List() { + log.Info().Msgf("Deleting secret: %s/%s", secret.Namespace, secret.Name) + err := m.client.Secrets().Delete(m.ctx, secret.Name, secret.Namespace) if err != nil { log.Error().Err(err).Msg("Failed to delete nats secret") } } - for _, secret := range secretsDemand.ToAdd { + for _, secret := range secretsDemand.ToAdd.List() { log.Info().Msgf("Creating secret: %s/%s", secret.Target.Namespace, secret.Target.Name) err := m.client.Secrets().Create(m.ctx, secret.Target) diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index 00c076c..a47732a 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -13,11 +13,12 @@ type Client struct { namespace string } -func New(database string, namespace string) (*Client, error) { +func New(database string, namespace string, password string) (*Client, error) { cfg := postgres.ConnectConfig{ Host: fmt.Sprintf("%s.%s.svc.cluster.local", database, namespace), Port: 26257, Username: "postgres", + Password: password, } conn, err := postgres.NewAdminConn(cfg) @@ -84,7 +85,7 @@ func (c *Client) DeleteDB(db Database) error { } func isReservedUser(name string) bool { - return name == "" || name == "admin" || name == "root" + return name == "" || name == "postgres" || name == "root" } func (c *Client) ListUsers() ([]User, error) { diff --git a/internal/dbs/postgres/k8s/secrets/secrets.go b/internal/dbs/postgres/k8s/secrets/secrets.go index cae187e..85082fb 100644 --- a/internal/dbs/postgres/k8s/secrets/secrets.go +++ b/internal/dbs/postgres/k8s/secrets/secrets.go @@ -29,6 +29,10 @@ type Cluster struct { Namespace string } +func (c Cluster) GetNamespace() string { + return c.Namespace + ":" + c.Name +} + type Comparable struct { Name string Namespace string diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index 481a810..32cfb3a 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -10,7 +10,6 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" - "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" "github.com/benjamin-wright/db-operator/internal/utils" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" @@ -57,7 +56,6 @@ func New( databases: bucket.NewBucket[database.Database](), users: bucket.NewBucket[database.User](), permissions: bucket.NewBucket[database.Permission](), - applied: bucket.NewBucket[database.Migration](), } return &Manager{ @@ -107,7 +105,7 @@ func (m *Manager) refreshDatabaseState() { m.state.ClearRemote() for _, client := range m.state.GetActiveClients() { - cli, err := database.New(client.Cluster.Name, client.Cluster.Namespace) + cli, err := database.New(client.Cluster.Name, client.Cluster.Namespace, "postgres") if err != nil { log.Error().Err(err).Msgf("Failed to create client for database %s/%s", client.Cluster.Namespace, client.Cluster.Name) continue @@ -168,146 +166,83 @@ func (m *Manager) refreshDatabaseState() { } } -func isUserSecret(user database.User, secret secrets.Resource) bool { - return user.Cluster.Name == secret.Cluster.Name && user.Cluster.Namespace == secret.Cluster.Namespace && user.Name == secret.User -} - -func setPasswords( - secretsDemand *state.Demand[clients.Resource, secrets.Resource], - userDemand *state.Demand[clients.Resource, database.User], - users bucket.Bucket[database.User], -) { - secretIds := []int{} - userIds := []int{} - for secretId, secret := range secretsDemand.ToAdd { - userId := -1 - - // check if the user already exists in the user demand - for id, user := range userDemand.ToAdd { - if isUserSecret(user.Target, secret.Target) { - userId = id - break - } - } - - // if the user does not exist in the user demand it must already exist, so delete the user and re-add it - if userId < 0 { - existing, _ := users.Get(secretsDemand.ToAdd[secretId].Target.User, secretsDemand.ToAdd[secretId].Target.Namespace) - - userDemand.ToRemove = append(userDemand.ToRemove, state.DemandTarget[clients.Resource, database.User]{ - Target: existing, - }) - userDemand.ToAdd = append(userDemand.ToAdd, state.DemandTarget[clients.Resource, database.User]{ - Parent: secret.Parent, - Target: existing, - }) - userId = len(userDemand.ToAdd) - 1 - } - - password, err := utils.GeneratePassword(32, true, true) - if err != nil { - // remove the secret and user from the ToAdd list - secretIds = append(secretIds, secretId) - userIds = append(userIds, userId) - log.Error().Err(err).Msgf("Failed to generate password for user: %v", err) - continue - } - - userDemand.ToAdd[userId].Target.Password = password - secretsDemand.ToAdd[secretId].Target.Password = password - } - - for _, secretId := range secretIds { - secretsDemand.ToAdd = append(secretsDemand.ToAdd[:secretId], secretsDemand.ToAdd[secretId+1:]...) - } - - for _, userId := range userIds { - userDemand.ToAdd = append(userDemand.ToAdd[:userId], userDemand.ToAdd[userId+1:]...) - } -} - func (m *Manager) processPostgresClients() { - dbDemand := m.state.GetDBDemand() - userDemand := m.state.GetUserDemand() - permsDemand := m.state.GetPermissionDemand() - secretsDemand := m.state.GetSecretsDemand() - - setPasswords(&secretsDemand, &userDemand, m.state.users) + dbDemand, userDemand, permsDemand, secretsDemand := m.state.GetDemand() dbs := newConsolidator() - for _, db := range dbDemand.ToAdd { + for _, db := range dbDemand.ToAdd.List() { dbs.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) } - for _, db := range dbDemand.ToRemove { - dbs.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) + for _, db := range dbDemand.ToRemove.List() { + dbs.add(db.Cluster.Name, db.Cluster.Namespace) } - for _, user := range userDemand.ToAdd { + for _, user := range userDemand.ToAdd.List() { dbs.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) } - for _, user := range userDemand.ToRemove { - dbs.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) + for _, user := range userDemand.ToRemove.List() { + dbs.add(user.Cluster.Name, user.Cluster.Namespace) } - for _, perm := range permsDemand.ToAdd { + for _, perm := range permsDemand.ToAdd.List() { dbs.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) } - for _, perm := range permsDemand.ToRemove { - dbs.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) + for _, perm := range permsDemand.ToRemove.List() { + dbs.add(perm.Cluster.Name, perm.Cluster.Namespace) } - for _, secret := range secretsDemand.ToRemove { - log.Info().Msgf("Removing secret %s", secret.Target.Name) - err := m.client.Secrets().Delete(m.ctx, secret.Target.Name, secret.Target.Namespace) + for _, secret := range secretsDemand.ToRemove.List() { + log.Info().Msgf("Removing secret %s", secret.Name) + err := m.client.Secrets().Delete(m.ctx, secret.Name, secret.Namespace) if err != nil { - log.Error().Err(err).Msgf("Failed to delete secret %s", secret.Target.Name) + log.Error().Err(err).Msgf("Failed to delete secret %s", secret.Name) } } for _, namespace := range dbs.getNamespaces() { for _, db := range dbs.getNames(namespace) { - cli, err := database.New(db, namespace) + cli, err := database.New(db, namespace, "postgres") if err != nil { log.Error().Err(err).Msgf("Failed to create database client for %s", db) continue } defer cli.Stop() - for _, perm := range permsDemand.ToRemove { - if perm.Target.Cluster.Name != db || perm.Target.Cluster.Namespace != namespace { + for _, perm := range permsDemand.ToRemove.List() { + if perm.Cluster.Name != db || perm.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) - err = cli.RevokePermission(perm.Target) + log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) + err = cli.RevokePermission(perm) if err != nil { - log.Error().Err(err).Msgf("Failed to revoke permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) + log.Error().Err(err).Msgf("Failed to revoke permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) } } - for _, toRemove := range dbDemand.ToRemove { - if toRemove.Target.Cluster.Name != db || toRemove.Target.Cluster.Namespace != namespace { + for _, toRemove := range dbDemand.ToRemove.List() { + if toRemove.Cluster.Name != db || toRemove.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Dropping database %s in db %s", toRemove.Target.Name, toRemove.Target.Cluster) - err = cli.DeleteDB(toRemove.Target) + log.Info().Msgf("Dropping database %s in db %s", toRemove.Name, toRemove.Cluster) + err = cli.DeleteDB(toRemove) if err != nil { - log.Error().Err(err).Msgf("Failed to delete database %s in db %s", toRemove.Target.Name, toRemove.Target.Cluster) + log.Error().Err(err).Msgf("Failed to delete database %s in db %s", toRemove.Name, toRemove.Cluster) } } - for _, user := range userDemand.ToRemove { - if user.Target.Cluster.Name != db || user.Target.Cluster.Namespace != namespace { + for _, user := range userDemand.ToRemove.List() { + if user.Cluster.Name != db || user.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Dropping user %s in db %s", user.Target.Name, user.Target.Cluster) - err = cli.DeleteUser(user.Target) + log.Info().Msgf("Dropping user %s in db %s", user.Name, user.Cluster) + err = cli.DeleteUser(user) if err != nil { - log.Error().Err(err).Msgf("Failed to delete user %s in db %s", user.Target.Name, user.Target.Cluster) + log.Error().Err(err).Msgf("Failed to delete user %s in db %s", user.Name, user.Cluster) } } - for _, toAdd := range dbDemand.ToAdd { + for _, toAdd := range dbDemand.ToAdd.List() { if toAdd.Target.Cluster.Name != db || toAdd.Target.Cluster.Namespace != namespace { continue } @@ -319,7 +254,7 @@ func (m *Manager) processPostgresClients() { } } - for _, user := range userDemand.ToAdd { + for _, user := range userDemand.ToAdd.List() { if user.Target.Cluster.Name != db || user.Target.Cluster.Namespace != namespace { continue } @@ -332,7 +267,7 @@ func (m *Manager) processPostgresClients() { } } - for _, perm := range permsDemand.ToAdd { + for _, perm := range permsDemand.ToAdd.List() { if perm.Target.Cluster.Name != db || perm.Target.Cluster.Namespace != namespace { continue } @@ -346,7 +281,7 @@ func (m *Manager) processPostgresClients() { } } - for _, secret := range secretsDemand.ToAdd { + for _, secret := range secretsDemand.ToAdd.List() { log.Info().Msgf("Adding secret %s", secret.Target.Name) err := m.client.Secrets().Create(m.ctx, secret.Target) if err != nil { diff --git a/internal/dbs/postgres/managers/database/manager_test.go b/internal/dbs/postgres/managers/database/manager_test.go deleted file mode 100644 index 36998ad..0000000 --- a/internal/dbs/postgres/managers/database/manager_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package database - -import ( - "testing" - - "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" - "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" - "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" - "github.com/benjamin-wright/db-operator/internal/state" - "github.com/benjamin-wright/db-operator/internal/state/bucket" - "github.com/stretchr/testify/assert" -) - -func secret(user string, cluster string, namespace string) state.DemandTarget[clients.Resource, secrets.Resource] { - return state.DemandTarget[clients.Resource, secrets.Resource]{ - Parent: clients.Resource{}, - Target: secrets.Resource{ - Comparable: secrets.Comparable{ - User: user, - Cluster: secrets.Cluster{ - Name: cluster, - Namespace: namespace, - }, - }, - }, - } -} - -func user(name string, cluster string, namespace string) state.DemandTarget[clients.Resource, database.User] { - return state.DemandTarget[clients.Resource, database.User]{ - Parent: clients.Resource{}, - Target: database.User{ - Name: name, - Cluster: database.Cluster{ - Name: cluster, - Namespace: namespace, - }, - }, - } -} - -func TestSetPasswords(t *testing.T) { - t.Run("set passwords", func(t *testing.T) { - secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ - ToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ - secret("user1", "cluster1", "namespace1"), - }, - ToRemove: []state.DemandTarget[clients.Resource, secrets.Resource]{}, - } - - usersDemand := state.Demand[clients.Resource, database.User]{ - ToAdd: []state.DemandTarget[clients.Resource, database.User]{ - user("user1", "cluster1", "namespace1"), - }, - ToRemove: []state.DemandTarget[clients.Resource, database.User]{}, - } - - setPasswords(&secretsDemand, &usersDemand, bucket.Bucket[database.User]{}) - - assert.Len(t, secretsDemand.ToAdd, 1) - assert.Len(t, secretsDemand.ToRemove, 0) - assert.Len(t, usersDemand.ToAdd, 1) - assert.Len(t, usersDemand.ToRemove, 0) - - password := usersDemand.ToAdd[0].Target.Password - assert.NotEmpty(t, password) - assert.Len(t, password, 32*2) - assert.Equal(t, password, secretsDemand.ToAdd[0].Target.Password) - }) - - t.Run("secret required but user already exists", func(t *testing.T) { - secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ - ToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ - secret("user1", "cluster1", "namespace1"), - }, - ToRemove: []state.DemandTarget[clients.Resource, secrets.Resource]{}, - } - - usersDemand := state.Demand[clients.Resource, database.User]{ - ToAdd: []state.DemandTarget[clients.Resource, database.User]{}, - ToRemove: []state.DemandTarget[clients.Resource, database.User]{}, - } - - existingUsers := bucket.NewBucket[database.User]() - existingUsers.Add(database.User{ - Name: "user1", - Cluster: database.Cluster{ - Name: "cluster1", - Namespace: "namespace1", - }, - }) - - setPasswords(&secretsDemand, &usersDemand, existingUsers) - - assert.Len(t, secretsDemand.ToAdd, 1) - assert.Len(t, secretsDemand.ToRemove, 0) - assert.Len(t, usersDemand.ToAdd, 1) - assert.Len(t, usersDemand.ToRemove, 1) - - password := usersDemand.ToAdd[0].Target.Password - - assert.NotEmpty(t, password) - assert.Len(t, password, 32*2) - assert.Equal(t, password, secretsDemand.ToAdd[0].Target.Password) - }) -} diff --git a/internal/dbs/postgres/managers/database/passwords.go b/internal/dbs/postgres/managers/database/passwords.go new file mode 100644 index 0000000..2fead00 --- /dev/null +++ b/internal/dbs/postgres/managers/database/passwords.go @@ -0,0 +1,123 @@ +package database + +// import ( +// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" +// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" +// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" +// "github.com/benjamin-wright/db-operator/internal/state" +// "github.com/benjamin-wright/db-operator/internal/state/bucket" +// "github.com/benjamin-wright/db-operator/internal/utils" +// ) + +// var generatePassword = utils.GeneratePassword + +// func isUserSecret(user database.User, secret secrets.Resource) bool { +// return user.Cluster.Name == secret.Cluster.Name && user.Cluster.Namespace == secret.Cluster.Namespace && user.Name == secret.User +// } + +// func alignSecretDemand( +// secretsDemand *state.Demand[clients.Resource, secrets.Resource], +// userDemand *state.Demand[clients.Resource, database.User], +// usersBucket bucket.Bucket[database.User], +// ) { +// for _, secret := range secretsDemand.ToAdd.List() { +// userId := -1 + +// // check if the user already exists in the user demand +// for id, user := range userDemand.ToAdd.List() { +// if isUserSecret(user.Target, secret.Target) { +// userId = id +// break +// } +// } + +// // if the user exists in the user demand then everything is good +// if userId >= 0 { +// continue +// } + +// // delete if already exists +// if existing, ok := usersBucket.Get(secret.Target.User, secret.Target.Cluster.TargetName()); ok { +// userDemand.ToRemove.Add(existing) +// } + +// // re-add the user to the user demand +// userDemand.ToAdd.Add(state.DemandTarget[clients.Resource, database.User]{ +// Parent: secret.Parent, +// Target: database.User{ +// Name: secret.Target.User, +// Cluster: database.Cluster{ +// Name: secret.Target.Cluster.Name, +// Namespace: secret.Target.Cluster.Namespace, +// }, +// }, +// }) +// } +// } + +// func alignUserDemand( +// secretsDemand *state.Demand[clients.Resource, secrets.Resource], +// userDemand *state.Demand[clients.Resource, database.User], +// secretsBucket bucket.Bucket[secrets.Resource], +// ) { +// for _, user := range userDemand.ToAdd.List() { +// secretId := -1 + +// // check if the secret already exists in the secret demand +// for id, secret := range secretsDemand.ToAdd.List() { +// if isUserSecret(user.Target, secret.Target) { +// secretId = id +// break +// } +// } + +// // if the secret exists in the secret demand then everything is good +// if secretId >= 0 { +// continue +// } + +// // delete if already exists +// if existing, ok := secretsBucket.Get(user.Parent.Secret, user.Parent.Namespace); ok { +// secretsDemand.ToRemove.Add(existing) +// } + +// // re-add the secret to the secret demand +// secretsDemand.ToAdd.Add(state.DemandTarget[clients.Resource, secrets.Resource]{ +// Parent: user.Parent, +// Target: secrets.Resource{ +// Comparable: secrets.Comparable{ +// Name: user.Parent.Secret, +// Namespace: user.Parent.Namespace, +// Database: user.Parent.Database, +// User: user.Target.Name, +// Cluster: secrets.Cluster{ +// Name: user.Target.Cluster.Name, +// Namespace: user.Target.Cluster.Namespace, +// }, +// }, +// }, +// }) +// } +// } + +// func setPasswords( +// secretsDemand *state.Demand[clients.Resource, secrets.Resource], +// userDemand *state.Demand[clients.Resource, database.User], +// usersBucket bucket.Bucket[database.User], +// secretsBucket bucket.Bucket[secrets.Resource], +// ) { +// alignSecretDemand(secretsDemand, userDemand, usersBucket) +// alignUserDemand(secretsDemand, userDemand, secretsBucket) + +// for secretId, secret := range secretsDemand.ToAdd.List() { +// for userId, user := range userDemand.ToAdd.List() { +// if isUserSecret(user.Target, secret.Target) { +// password := generatePassword(32, true, true) +// userDemand.ToAdd.List()[userId].Target.Password = password +// secretsDemand.ToAdd.List()[secretId].Target.Password = password +// break +// } +// } + +// } +// } diff --git a/internal/dbs/postgres/managers/database/passwords_test.go b/internal/dbs/postgres/managers/database/passwords_test.go new file mode 100644 index 0000000..6b9b61f --- /dev/null +++ b/internal/dbs/postgres/managers/database/passwords_test.go @@ -0,0 +1,227 @@ +package database + +// import ( +// "fmt" +// "testing" + +// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" +// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" +// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" +// "github.com/benjamin-wright/db-operator/internal/state" +// "github.com/benjamin-wright/db-operator/internal/state/bucket" +// "github.com/stretchr/testify/assert" +// ) + +// func secret(id int) secrets.Resource { +// return secrets.Resource{ +// Comparable: secrets.Comparable{ +// Name: fmt.Sprintf("secret%d", id), +// Namespace: fmt.Sprintf("namespace%d", id), +// User: fmt.Sprintf("user%d", id), +// Database: fmt.Sprintf("database%d", id), +// Cluster: secrets.Cluster{ +// Name: fmt.Sprintf("cluster%d", id), +// Namespace: fmt.Sprintf("cluster-namespace%d", id), +// }, +// }, +// } +// } + +// func user(id int) database.User { +// return database.User{ +// Name: fmt.Sprintf("user%d", id), +// Cluster: database.Cluster{ +// Name: fmt.Sprintf("cluster%d", id), +// Namespace: fmt.Sprintf("cluster-namespace%d", id), +// }, +// } +// } + +// func client(id int) clients.Resource { +// return clients.Resource{ +// Comparable: clients.Comparable{ +// Username: fmt.Sprintf("user%d", id), +// Secret: fmt.Sprintf("secret%d", id), +// Namespace: fmt.Sprintf("namespace%d", id), +// Database: fmt.Sprintf("database%d", id), +// Cluster: clients.Cluster{ +// Name: fmt.Sprintf("cluster%d", id), +// Namespace: fmt.Sprintf("cluster-namespace%d", id), +// }, +// }, +// } +// } + +// func secretDemand(id int) state.DemandTarget[clients.Resource, secrets.Resource] { +// return state.DemandTarget[clients.Resource, secrets.Resource]{ +// Parent: client(id), +// Target: secret(id), +// } +// } + +// func secretWithPassword(id int, password string) state.DemandTarget[clients.Resource, secrets.Resource] { +// secret := secretDemand(id) +// secret.Target.Password = password +// return secret +// } + +// func userDemand(id int) state.DemandTarget[clients.Resource, database.User] { +// return state.DemandTarget[clients.Resource, database.User]{ +// Parent: client(id), +// Target: user(id), +// } +// } + +// func userWithPassword(id int, password string) state.DemandTarget[clients.Resource, database.User] { +// user := userDemand(id) +// user.Target.Password = password +// return user +// } + +// func TestSetPasswords(t *testing.T) { +// generatePassword = func(int, bool, bool) string { +// return "password" +// } + +// type testDemand struct { +// secretsToAdd bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]] +// secretsToRemove []secrets.Resource +// usersToAdd []state.DemandTarget[clients.Resource, database.User] +// usersToRemove []database.User +// } + +// tests := []struct { +// name string +// input testDemand +// existingUsers []database.User +// existingSecrets []secrets.Resource +// expected testDemand +// }{ +// { +// name: "set passwords", +// input: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretDemand(1), +// }, +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userDemand(1), +// }, +// }, +// expected: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretWithPassword(1, "password"), +// }, +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userWithPassword(1, "password"), +// }, +// }, +// }, +// { +// name: "secret required but user missing from demand", +// input: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretDemand(1), +// }, +// }, +// existingUsers: []database.User{ +// user(1), +// }, +// expected: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretWithPassword(1, "password"), +// }, +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userWithPassword(1, "password"), +// }, +// usersToRemove: []database.User{ +// user(1), +// }, +// }, +// }, +// { +// name: "secret required but user missing from demand and doesn't already exist", +// input: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretDemand(1), +// }, +// }, +// expected: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretWithPassword(1, "password"), +// }, +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userWithPassword(1, "password"), +// }, +// }, +// }, +// { +// name: "user required but secret missing from demand", +// input: testDemand{ +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userDemand(1), +// }, +// }, +// existingSecrets: []secrets.Resource{ +// secret(1), +// }, +// expected: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretWithPassword(1, "password"), +// }, +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userWithPassword(1, "password"), +// }, +// secretsToRemove: []secrets.Resource{ +// secret(1), +// }, +// }, +// }, +// { +// name: "user required but secret missing from demand and doesn't already exist", +// input: testDemand{ +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userDemand(1), +// }, +// }, +// expected: testDemand{ +// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ +// secretWithPassword(1, "password"), +// }, +// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ +// userWithPassword(1, "password"), +// }, +// }, +// }, +// } + +// for _, test := range tests { +// t.Run(test.name, func(t *testing.T) { +// secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ +// ToAdd: test.input.secretsToAdd, +// ToRemove: test.input.secretsToRemove, +// } + +// usersDemand := state.Demand[clients.Resource, database.User]{ +// ToAdd: test.input.usersToAdd, +// ToRemove: test.input.usersToRemove, +// } + +// existingUsers := bucket.NewBucket[database.User]() +// for _, user := range test.existingUsers { +// existingUsers.Add(user) +// } + +// existingSecrets := bucket.NewBucket[secrets.Resource]() +// for _, secret := range test.existingSecrets { +// existingSecrets.Add(secret) +// } + +// setPasswords(&secretsDemand, &usersDemand, existingUsers, existingSecrets) + +// assert.Equal(t, test.expected.secretsToAdd, secretsDemand.ToAdd, "secretsToAdd") +// assert.Equal(t, test.expected.secretsToRemove, secretsDemand.ToRemove, "secretsToRemove") +// assert.Equal(t, test.expected.usersToAdd, usersDemand.ToAdd, "usersToAdd") +// assert.Equal(t, test.expected.usersToRemove, usersDemand.ToRemove, "usersToRemove") +// }) +// } +// } diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 916d0cc..5c29b68 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -7,6 +7,7 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" "github.com/benjamin-wright/db-operator/internal/state" "github.com/benjamin-wright/db-operator/internal/state/bucket" + "github.com/benjamin-wright/db-operator/internal/utils" "github.com/benjamin-wright/db-operator/pkg/k8s_generic" "github.com/rs/zerolog/log" ) @@ -18,7 +19,6 @@ type State struct { databases bucket.Bucket[database.Database] users bucket.Bucket[database.User] permissions bucket.Bucket[database.Permission] - applied bucket.Bucket[database.Migration] } func (s *State) Apply(update interface{}) { @@ -35,8 +35,6 @@ func (s *State) Apply(update interface{}) { s.users.Apply(u) case k8s_generic.Update[database.Permission]: s.permissions.Apply(u) - case k8s_generic.Update[database.Migration]: - s.applied.Apply(u) default: log.Logger.Error().Interface("update", u).Msg("wat dis? Unknown state update") } @@ -46,97 +44,232 @@ func (s *State) ClearRemote() { s.databases.Clear() s.users.Clear() s.permissions.Clear() - s.applied.Clear() } -func (s *State) GetActiveClients() []clients.Resource { - clients := []clients.Resource{} +func (s *State) getRequests() ( + bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]], + bucket.Bucket[state.DemandTarget[clients.Resource, database.User]], + bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], + bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]], +) { + databaseRequests := bucket.NewBucket[state.DemandTarget[clients.Resource, database.Database]]() + userRequests := bucket.NewBucket[state.DemandTarget[clients.Resource, database.User]]() + permissionRequests := bucket.NewBucket[state.DemandTarget[clients.Resource, database.Permission]]() + secretRequests := bucket.NewBucket[state.DemandTarget[clients.Resource, secrets.Resource]]() for _, client := range s.clients.List() { target := client.GetTarget() targetNamespace := client.GetTargetNamespace() + statefulSet, hasSS := s.statefulSets.Get(target, targetNamespace) if !hasSS || !statefulSet.IsReady() { continue } - clients = append(clients, client) - } - - return clients -} - -func (s *State) GetDBDemand() state.Demand[clients.Resource, database.Database] { - return state.GetServiceBound( - s.clients, - s.databases, - s.statefulSets, - func(client clients.Resource) database.Database { - return database.Database{ + databaseRequests.Add(state.DemandTarget[clients.Resource, database.Database]{ + Parent: client, + Target: database.Database{ Name: client.Database, Cluster: database.Cluster{ Name: client.Cluster.Name, Namespace: client.Cluster.Namespace, }, - } - }, - ) -} + }, + }) -func (s *State) GetUserDemand() state.Demand[clients.Resource, database.User] { - return state.GetServiceBound( - s.clients, - s.users, - s.statefulSets, - func(client clients.Resource) database.User { - return database.User{ + userRequests.Add(state.DemandTarget[clients.Resource, database.User]{ + Parent: client, + Target: database.User{ Name: client.Username, Cluster: database.Cluster{ Name: client.Cluster.Name, Namespace: client.Cluster.Namespace, }, - } - }, - ) -} + }, + }) -func (s *State) GetPermissionDemand() state.Demand[clients.Resource, database.Permission] { - return state.GetServiceBound( - s.clients, - s.permissions, - s.statefulSets, - func(client clients.Resource) database.Permission { - return database.Permission{ + permissionRequests.Add(state.DemandTarget[clients.Resource, database.Permission]{ + Parent: client, + Target: database.Permission{ User: client.Username, Database: client.Database, Cluster: database.Cluster{ Name: client.Cluster.Name, Namespace: client.Cluster.Namespace, }, - } - }, - ) -} + }, + }) -func (s *State) GetSecretsDemand() state.Demand[clients.Resource, secrets.Resource] { - return state.GetServiceBound( - s.clients, - s.secrets, - s.statefulSets, - func(client clients.Resource) secrets.Resource { - return secrets.Resource{ + secretRequests.Add(state.DemandTarget[clients.Resource, secrets.Resource]{ + Parent: client, + Target: secrets.Resource{ Comparable: secrets.Comparable{ Name: client.Secret, Namespace: client.Namespace, + Database: client.Database, + User: client.Username, Cluster: secrets.Cluster{ Name: client.Cluster.Name, Namespace: client.Cluster.Namespace, }, - Database: client.Database, - User: client.Username, }, + }, + }) + } + + return databaseRequests, userRequests, permissionRequests, secretRequests +} + +func (s *State) diffDatabases(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]]) state.Demand[clients.Resource, database.Database] { + demand := state.NewDemand[clients.Resource, database.Database]() + + for _, db := range requests.List() { + if _, ok := s.databases.Get(db.Target.GetName(), db.Target.GetNamespace()); !ok { + demand.ToAdd.Add(db) + } + } + + for _, db := range s.databases.List() { + if _, ok := requests.Get(db.GetName(), db.GetNamespace()); !ok { + demand.ToRemove.Add(db) + } + } + + return demand +} + +func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.User]]) state.Demand[clients.Resource, database.User] { + demand := state.Demand[clients.Resource, database.User]{ + ToAdd: bucket.NewBucket[state.DemandTarget[clients.Resource, database.User]](), + ToRemove: bucket.NewBucket[database.User](), + } + + for _, userRequest := range requests.List() { + _, userExists := s.users.Get(userRequest.Target.GetName(), userRequest.Target.GetNamespace()) + _, secretExists := s.secrets.Get(userRequest.Parent.Secret, userRequest.Parent.Namespace) + + if userExists && !secretExists { + demand.ToRemove.Add(userRequest.Target) + userExists = false + } + + if !userExists { + userRequest.Target.Password = utils.GeneratePassword(32, true, true) + demand.ToAdd.Add(userRequest) + } + } + + for _, user := range s.users.List() { + if _, ok := requests.Get(user.GetName(), user.GetNamespace()); !ok { + demand.ToRemove.Add(user) + } + } + + return demand +} + +func (s *State) diffPermissions( + requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], + deadUsers bucket.Bucket[database.User], +) state.Demand[clients.Resource, database.Permission] { + demand := state.Demand[clients.Resource, database.Permission]{ + ToAdd: bucket.NewBucket[state.DemandTarget[clients.Resource, database.Permission]](), + ToRemove: bucket.NewBucket[database.Permission](), + } + + for _, permissionRequest := range requests.List() { + _, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) + _, isRefreshing := deadUsers.Get(permissionRequest.Parent.Username, permissionRequest.Target.GetNamespace()) + + if !permissionExists { + demand.ToAdd.Add(permissionRequest) + } + + if permissionExists && isRefreshing { + demand.ToRemove.Add(permissionRequest.Target) + demand.ToAdd.Add(permissionRequest) + } + } + + for _, permission := range s.permissions.List() { + if _, ok := requests.Get(permission.GetName(), permission.GetNamespace()); !ok { + demand.ToRemove.Add(permission) + } + } + + return demand +} + +func (s *State) diffSecrets( + requests bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]], + users state.Demand[clients.Resource, database.User], +) state.Demand[clients.Resource, secrets.Resource] { + demand := state.Demand[clients.Resource, secrets.Resource]{ + ToAdd: bucket.NewBucket[state.DemandTarget[clients.Resource, secrets.Resource]](), + ToRemove: bucket.NewBucket[secrets.Resource](), + } + + for _, secretRequest := range requests.List() { + _, secretExists := s.secrets.Get(secretRequest.Target.GetName(), secretRequest.Target.GetNamespace()) + _, isRefreshing := users.ToRemove.Get(secretRequest.Parent.Username, secretRequest.Target.Cluster.GetNamespace()) + + if secretExists && isRefreshing { + demand.ToRemove.Add(secretRequest.Target) + secretExists = false + } + + if !secretExists { + user, ok := users.ToAdd.Get(secretRequest.Parent.Username, secretRequest.Target.Cluster.GetNamespace()) + if !ok { + log.Logger.Error().Str("secret", secretRequest.Target.GetName()).Msg("wat dis? User not found for secret") + continue } - }, - ) + + secretRequest.Target.Password = user.Target.Password + demand.ToAdd.Add(secretRequest) + } + } + + for _, secret := range s.secrets.List() { + if _, ok := requests.Get(secret.GetName(), secret.GetNamespace()); !ok { + demand.ToRemove.Add(secret) + } + } + + return demand +} + +func (s *State) GetDemand() ( + state.Demand[clients.Resource, database.Database], + state.Demand[clients.Resource, database.User], + state.Demand[clients.Resource, database.Permission], + state.Demand[clients.Resource, secrets.Resource], +) { + dbRequests, userRequests, permissionRequests, secretRequests := s.getRequests() + + dbDemand := s.diffDatabases(dbRequests) + userDemand := s.diffUsers(userRequests) + permissionDemand := s.diffPermissions(permissionRequests, userDemand.ToRemove) + secretsDemand := s.diffSecrets(secretRequests, userDemand) + + return dbDemand, userDemand, permissionDemand, secretsDemand +} + +func (s *State) GetActiveClients() []clients.Resource { + clients := []clients.Resource{} + + for _, client := range s.clients.List() { + target := client.GetTarget() + targetNamespace := client.GetTargetNamespace() + statefulSet, hasSS := s.statefulSets.Get(target, targetNamespace) + + if !hasSS || !statefulSet.IsReady() { + continue + } + + clients = append(clients, client) + } + + return clients } diff --git a/internal/dbs/postgres/managers/deployment/manager.go b/internal/dbs/postgres/managers/deployment/manager.go index 77c13cc..c828583 100644 --- a/internal/dbs/postgres/managers/deployment/manager.go +++ b/internal/dbs/postgres/managers/deployment/manager.go @@ -105,18 +105,18 @@ func (m *Manager) processPostgresDBs() { svcDemand := m.state.GetServiceDemand() pvcsToRemove := m.state.GetPVCDemand() - for _, db := range ssDemand.ToRemove { - log.Info().Msgf("Deleting db: %s", db.Target.Name) - err := m.client.StatefulSets().Delete(m.ctx, db.Target.Name, db.Target.Namespace) + for _, db := range ssDemand.ToRemove.List() { + log.Info().Msgf("Deleting db: %s", db.Name) + err := m.client.StatefulSets().Delete(m.ctx, db.Name, db.Namespace) if err != nil { log.Error().Err(err).Msgf("Failed to delete postgresdb stateful set: %+v", err) } } - for _, svc := range svcDemand.ToRemove { - log.Info().Msgf("Deleting service: %s", svc.Target.Name) - err := m.client.Services().Delete(m.ctx, svc.Target.Name, svc.Target.Namespace) + for _, svc := range svcDemand.ToRemove.List() { + log.Info().Msgf("Deleting service: %s", svc.Name) + err := m.client.Services().Delete(m.ctx, svc.Name, svc.Namespace) if err != nil { log.Error().Err(err).Msgf("Failed to delete postgresdb service: %+v", err) @@ -132,7 +132,7 @@ func (m *Manager) processPostgresDBs() { } } - for _, db := range ssDemand.ToAdd { + for _, db := range ssDemand.ToAdd.List() { log.Info().Msgf("Creating db: %s", db.Target.Name) err := m.client.StatefulSets().Create(m.ctx, db.Target) if err != nil { @@ -143,7 +143,7 @@ func (m *Manager) processPostgresDBs() { } } - for _, svc := range svcDemand.ToAdd { + for _, svc := range svcDemand.ToAdd.List() { log.Info().Msgf("Creating service: %s", svc.Target.Name) err := m.client.Services().Create(m.ctx, svc.Target) diff --git a/internal/dbs/redis/manager/manager.go b/internal/dbs/redis/manager/manager.go index c09fc5e..0403c1f 100644 --- a/internal/dbs/redis/manager/manager.go +++ b/internal/dbs/redis/manager/manager.go @@ -109,18 +109,18 @@ func (m *Manager) processRedisDBs() { svcDemand := m.state.GetServiceDemand() pvcsToRemove := m.state.GetPVCDemand() - for _, db := range ssDemand.ToRemove { - log.Info().Msgf("Deleting db: %s/%s", db.Target.Namespace, db.Target.Name) - err := m.client.StatefulSets().Delete(m.ctx, db.Target.Name, db.Target.Namespace) + for _, db := range ssDemand.ToRemove.List() { + log.Info().Msgf("Deleting db: %s/%s", db.Namespace, db.Name) + err := m.client.StatefulSets().Delete(m.ctx, db.Name, db.Namespace) if err != nil { log.Error().Err(err).Msg("Failed to delete redis stateful set") } } - for _, svc := range svcDemand.ToRemove { - log.Info().Msgf("Deleting service: %s/%s", svc.Target.Namespace, svc.Target.Name) - err := m.client.Services().Delete(m.ctx, svc.Target.Name, svc.Target.Namespace) + for _, svc := range svcDemand.ToRemove.List() { + log.Info().Msgf("Deleting service: %s/%s", svc.Namespace, svc.Name) + err := m.client.Services().Delete(m.ctx, svc.Name, svc.Namespace) if err != nil { log.Error().Err(err).Msg("Failed to delete redis service") @@ -136,7 +136,7 @@ func (m *Manager) processRedisDBs() { } } - for _, db := range ssDemand.ToAdd { + for _, db := range ssDemand.ToAdd.List() { log.Info().Msgf("Creating db: %s", db.Target.Name) err := m.client.StatefulSets().Create(m.ctx, db.Target) if err != nil { @@ -147,7 +147,7 @@ func (m *Manager) processRedisDBs() { } } - for _, svc := range svcDemand.ToAdd { + for _, svc := range svcDemand.ToAdd.List() { log.Info().Msgf("Creating service: %s/%s", svc.Target.Namespace, svc.Target.Name) err := m.client.Services().Create(m.ctx, svc.Target) @@ -160,16 +160,16 @@ func (m *Manager) processRedisDBs() { func (m *Manager) processRedisStatefulSets() { secretsDemand := m.state.GetSecretsDemand() - for _, secret := range secretsDemand.ToRemove { - log.Info().Msgf("Deleting secret: %s/%s", secret.Target.Namespace, secret.Target.Name) - err := m.client.Secrets().Delete(m.ctx, secret.Target.Name, secret.Target.Namespace) + for _, secret := range secretsDemand.ToRemove.List() { + log.Info().Msgf("Deleting secret: %s/%s", secret.Namespace, secret.Name) + err := m.client.Secrets().Delete(m.ctx, secret.Name, secret.Namespace) if err != nil { log.Error().Err(err).Msg("Failed to delete redis secret") } } - for _, secret := range secretsDemand.ToAdd { + for _, secret := range secretsDemand.ToAdd.List() { log.Info().Msgf("Creating secret: %s/%s", secret.Target.Namespace, secret.Target.Name) err := m.client.Secrets().Create(m.ctx, secret.Target) diff --git a/internal/state/demand.go b/internal/state/demand.go index ae817c0..b01f7a7 100644 --- a/internal/state/demand.go +++ b/internal/state/demand.go @@ -5,32 +5,47 @@ import ( "github.com/benjamin-wright/db-operator/internal/state/types" ) -type DemandTarget[T any, U any] struct { +type DemandTarget[T types.Nameable, U types.Nameable] struct { Parent T Target U } -type Demand[T any, U any] struct { - ToAdd []DemandTarget[T, U] - ToRemove []DemandTarget[T, U] +func (d DemandTarget[T, U]) GetName() string { + return d.Target.GetName() +} + +func (d DemandTarget[T, U]) GetNamespace() string { + return d.Target.GetNamespace() +} + +type Demand[T types.Nameable, U types.Nameable] struct { + ToAdd bucket.Bucket[DemandTarget[T, U]] + ToRemove bucket.Bucket[U] +} + +func NewDemand[T types.Nameable, U types.Nameable]() Demand[T, U] { + return Demand[T, U]{ + ToAdd: bucket.NewBucket[DemandTarget[T, U]](), + ToRemove: bucket.NewBucket[U](), + } } func GetOneForOne[ T types.Nameable, U types.Nameable, ](request bucket.Bucket[T], existing bucket.Bucket[U], transform func(T) U) Demand[T, U] { - toAdd := []DemandTarget[T, U]{} - toRemove := []DemandTarget[T, U]{} + toAdd := bucket.NewBucket[DemandTarget[T, U]]() + toRemove := bucket.NewBucket[U]() for _, obj := range request.List() { if _, ok := existing.Get(obj.GetName(), obj.GetNamespace()); !ok { - toAdd = append(toAdd, DemandTarget[T, U]{Parent: obj, Target: transform(obj)}) + toAdd.Add(DemandTarget[T, U]{Parent: obj, Target: transform(obj)}) } } for _, obj := range existing.List() { if _, ok := request.Get(obj.GetName(), obj.GetNamespace()); !ok { - toRemove = append(toRemove, DemandTarget[T, U]{Target: obj}) + toRemove.Add(obj) } } @@ -72,23 +87,23 @@ func GetStorageBound[ existing bucket.Bucket[U], transform func(T) U, ) Demand[T, U] { - toAdd := []DemandTarget[T, U]{} - toRemove := []DemandTarget[T, U]{} + toAdd := bucket.NewBucket[DemandTarget[T, U]]() + toRemove := bucket.NewBucket[U]() for _, db := range demand.List() { if ss, ok := existing.Get(db.GetName(), db.GetNamespace()); !ok { - toAdd = append(toAdd, DemandTarget[T, U]{Parent: db, Target: transform(db)}) + toAdd.Add(DemandTarget[T, U]{Parent: db, Target: transform(db)}) } else { if db.GetStorage() != ss.GetStorage() { - toRemove = append(toRemove, DemandTarget[T, U]{Parent: db, Target: transform(db)}) - toAdd = append(toAdd, DemandTarget[T, U]{Parent: db, Target: transform(db)}) + toRemove.Add(transform(db)) + toAdd.Add(DemandTarget[T, U]{Parent: db, Target: transform(db)}) } } } for _, db := range existing.List() { if _, ok := demand.Get(db.GetName(), db.GetNamespace()); !ok { - toRemove = append(toRemove, DemandTarget[T, U]{Target: db}) + toRemove.Add(db) } } @@ -105,8 +120,8 @@ func GetServiceBound[T types.Targetable, U types.Nameable, V types.Readyable]( transform func(T) U, ) Demand[T, U] { d := Demand[T, U]{ - ToAdd: []DemandTarget[T, U]{}, - ToRemove: []DemandTarget[T, U]{}, + ToAdd: bucket.NewBucket[DemandTarget[T, U]](), + ToRemove: bucket.NewBucket[U](), } seen := bucket.NewBucket[U]() @@ -122,13 +137,13 @@ func GetServiceBound[T types.Targetable, U types.Nameable, V types.Readyable]( seen.Add(desired) if _, ok := existing.Get(desired.GetName(), desired.GetNamespace()); !ok { - d.ToAdd = append(d.ToAdd, DemandTarget[T, U]{Parent: client, Target: desired}) + d.ToAdd.Add(DemandTarget[T, U]{Parent: client, Target: desired}) } } for _, e := range existing.List() { if _, ok := seen.Get(e.GetName(), e.GetNamespace()); !ok { - d.ToRemove = append(d.ToRemove, DemandTarget[T, U]{Target: e}) + d.ToRemove.Add(e) } } diff --git a/internal/utils/passwords.go b/internal/utils/passwords.go index 2dbfdae..4eba16d 100644 --- a/internal/utils/passwords.go +++ b/internal/utils/passwords.go @@ -5,7 +5,7 @@ import ( "math/big" ) -func GeneratePassword(length int, includeNumeric bool, includeSpecial bool) (string, error) { +func GeneratePassword(length int, includeNumeric bool, includeSpecial bool) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const numeric = "0123456789" const special = "!@#$%^&*()_+=-" @@ -30,5 +30,5 @@ func GeneratePassword(length int, includeNumeric bool, includeSpecial bool) (str } password = append(password, charSource[randNum.Int64()]) } - return string(password), nil + return string(password) } diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 796f9b9..f741eac 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -43,7 +43,7 @@ func (d *AdminConn) Stop() { } func (d *AdminConn) ListUsers() ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SHOW USERS") + rows, err := d.conn.Query(context.Background(), "SELECT usename FROM pg_catalog.pg_user") if err != nil { return nil, fmt.Errorf("failed to list users: %+v", err) } @@ -53,7 +53,7 @@ func (d *AdminConn) ListUsers() ([]string, error) { for rows.Next() { var user string - if err := rows.Scan(&user, nil, nil); err != nil { + if err := rows.Scan(&user); err != nil { return nil, fmt.Errorf("failed to decode database user: %+v", err) } From 5587a4f83eb65336751a81009b1360f53b200ec4 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sun, 19 May 2024 16:59:57 +0100 Subject: [PATCH 08/22] working through SQL changes --- internal/dbs/postgres/managers/database/state.go | 15 +++------------ internal/utils/passwords.go | 2 +- pkg/postgres/admin.go | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 5c29b68..46e7719 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -140,10 +140,7 @@ func (s *State) diffDatabases(requests bucket.Bucket[state.DemandTarget[clients. } func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.User]]) state.Demand[clients.Resource, database.User] { - demand := state.Demand[clients.Resource, database.User]{ - ToAdd: bucket.NewBucket[state.DemandTarget[clients.Resource, database.User]](), - ToRemove: bucket.NewBucket[database.User](), - } + demand := state.NewDemand[clients.Resource, database.User]() for _, userRequest := range requests.List() { _, userExists := s.users.Get(userRequest.Target.GetName(), userRequest.Target.GetNamespace()) @@ -173,10 +170,7 @@ func (s *State) diffPermissions( requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], deadUsers bucket.Bucket[database.User], ) state.Demand[clients.Resource, database.Permission] { - demand := state.Demand[clients.Resource, database.Permission]{ - ToAdd: bucket.NewBucket[state.DemandTarget[clients.Resource, database.Permission]](), - ToRemove: bucket.NewBucket[database.Permission](), - } + demand := state.NewDemand[clients.Resource, database.Permission]() for _, permissionRequest := range requests.List() { _, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) @@ -205,10 +199,7 @@ func (s *State) diffSecrets( requests bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]], users state.Demand[clients.Resource, database.User], ) state.Demand[clients.Resource, secrets.Resource] { - demand := state.Demand[clients.Resource, secrets.Resource]{ - ToAdd: bucket.NewBucket[state.DemandTarget[clients.Resource, secrets.Resource]](), - ToRemove: bucket.NewBucket[secrets.Resource](), - } + demand := state.NewDemand[clients.Resource, secrets.Resource]() for _, secretRequest := range requests.List() { _, secretExists := s.secrets.Get(secretRequest.Target.GetName(), secretRequest.Target.GetNamespace()) diff --git a/internal/utils/passwords.go b/internal/utils/passwords.go index 4eba16d..1741899 100644 --- a/internal/utils/passwords.go +++ b/internal/utils/passwords.go @@ -28,7 +28,7 @@ func GeneratePassword(length int, includeNumeric bool, includeSpecial bool) stri if err != nil { panic(err) } - password = append(password, charSource[randNum.Int64()]) + password[i] = charSource[randNum.Int64()] } return string(password) } diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index f741eac..7e09189 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -66,7 +66,7 @@ func (d *AdminConn) ListUsers() ([]string, error) { func (d *AdminConn) CreateUser(username string, password string) error { log.Info().Msgf("Creating user %s", username) if password != "" { - if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)+" WITH PASSWORD $1", password); err != nil { + if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)+" WITH PASSWORD '"+sanitize(password)+"'"); err != nil { return fmt.Errorf("failed to create database user: %+v", err) } } else { From 75a2d2a65ea7b53559617143790fcf53c44e3690 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sun, 19 May 2024 22:46:00 +0100 Subject: [PATCH 09/22] working with permissions --- internal/dbs/postgres/database/client.go | 22 ++-- .../dbs/postgres/managers/database/manager.go | 112 +++++++++--------- .../dbs/postgres/managers/database/state.go | 19 ++- pkg/postgres/admin.go | 28 ++--- 4 files changed, 88 insertions(+), 93 deletions(-) diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index a47732a..1125c38 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -2,6 +2,7 @@ package database import ( "fmt" + "strings" "github.com/benjamin-wright/db-operator/pkg/postgres" "github.com/rs/zerolog/log" @@ -9,37 +10,38 @@ import ( type Client struct { conn *postgres.AdminConn - database string + cluster string namespace string } -func New(database string, namespace string, password string) (*Client, error) { +func New(cluster string, namespace string, password string, database string) (*Client, error) { cfg := postgres.ConnectConfig{ - Host: fmt.Sprintf("%s.%s.svc.cluster.local", database, namespace), + Host: fmt.Sprintf("%s.%s.svc.cluster.local", cluster, namespace), Port: 26257, Username: "postgres", Password: password, + Database: database, } conn, err := postgres.NewAdminConn(cfg) if err != nil { - return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", database, err) + return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", cluster, err) } return &Client{ conn: conn, - database: database, + cluster: cluster, namespace: namespace, }, nil } func (c *Client) Stop() { - log.Info().Msgf("Closing connection to DB %s", c.database) + log.Info().Msgf("Closing connection to DB %s", c.cluster) c.conn.Stop() } func isReservedDB(name string) bool { - return name == "system" || name == "postgres" + return name == "system" || name == "postgres" || strings.HasPrefix(name, "template") } func (c *Client) ListDBs() ([]Database, error) { @@ -56,7 +58,7 @@ func (c *Client) ListDBs() ([]Database, error) { databases = append(databases, Database{ Cluster: Cluster{ - Name: c.database, + Name: c.cluster, Namespace: c.namespace, }, Name: name, @@ -102,7 +104,7 @@ func (c *Client) ListUsers() ([]User, error) { users = append(users, User{ Cluster: Cluster{ - Name: c.database, + Name: c.cluster, Namespace: c.namespace, }, Name: name, @@ -144,7 +146,7 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { permissions = append(permissions, Permission{ Cluster: Cluster{ - Name: c.database, + Name: c.cluster, Namespace: c.namespace, }, Database: db.Name, diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index 32cfb3a..c32ee2b 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -104,64 +104,42 @@ func (m *Manager) refresh() { func (m *Manager) refreshDatabaseState() { m.state.ClearRemote() - for _, client := range m.state.GetActiveClients() { - cli, err := database.New(client.Cluster.Name, client.Cluster.Namespace, "postgres") + for _, cluster := range m.state.GetActiveClusters() { + globalCli, err := database.New(cluster.Name, cluster.Namespace, "postgres", "") if err != nil { - log.Error().Err(err).Msgf("Failed to create client for database %s/%s", client.Cluster.Namespace, client.Cluster.Name) + log.Error().Err(err).Msgf("Failed to create client for database %s/%s", cluster.Namespace, cluster.Name) continue } - defer cli.Stop() + defer globalCli.Stop() - users, err := cli.ListUsers() + users, err := globalCli.ListUsers() if err != nil { - log.Error().Err(err).Msgf("Failed to list users in %s/%s", client.Cluster.Namespace, client.Cluster.Name) + log.Error().Err(err).Msgf("Failed to list users in %s/%s", cluster.Namespace, cluster.Name) continue } - m.state.Apply(k8s_generic.Update[database.User]{ToAdd: users}) - names, err := cli.ListDBs() + dbs, err := globalCli.ListDBs() if err != nil { - log.Error().Err(err).Msgf("Failed to list databases in %s/%s", client.Cluster.Namespace, client.Cluster.Name) + log.Error().Err(err).Msgf("Failed to list databases in %s/%s", cluster.Namespace, cluster.Name) continue } + m.state.Apply(k8s_generic.Update[database.Database]{ToAdd: dbs}) - for _, db := range names { - m.state.Apply(k8s_generic.Update[database.Database]{ToAdd: []database.Database{db}}) + for _, db := range dbs { + cli, err := database.New(db.Cluster.Name, db.Cluster.Namespace, "postgres", db.Name) + if err != nil { + log.Error().Err(err).Msgf("Failed to create client for database %s/%s", db.Cluster.Namespace, db.Cluster.Name) + continue + } + defer cli.Stop() permissions, err := cli.ListPermitted(db) if err != nil { - log.Error().Err(err).Msgf("Failed to list permissions in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) + log.Error().Err(err).Msgf("Failed to list permissions in %s/%s/%s", db.Cluster.Namespace, db.Cluster.Name, db.Name) continue } - m.state.Apply(k8s_generic.Update[database.Permission]{ToAdd: permissions}) - - // mClient, err := database.NewMigrations(db.Cluster.Name, db.Cluster.Namespace, db.Name) - // if err != nil { - // log.Error().Err(err).Msgf("Failed to get migration client in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) - // continue - // } - // defer mClient.Stop() - - // if ok, err := mClient.HasMigrationsTable(); err != nil { - // log.Error().Err(err).Msgf("Failed to check for migrations table in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) - // continue - // } else if !ok { - // err = mClient.CreateMigrationsTable() - // if err != nil { - // log.Error().Err(err).Msgf("Failed to create migrations table in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) - // continue - // } - // } - - // migrations, err := mClient.AppliedMigrations() - // if err != nil { - // log.Error().Err(err).Msgf("Failed to get applied migrations in %s/%s/%s", client.Cluster.Namespace, client.Cluster.Name, db.Name) - // continue - // } - - // m.state.Apply(k8s_generic.Update[database.Migration]{ToAdd: migrations}) } } } @@ -169,24 +147,24 @@ func (m *Manager) refreshDatabaseState() { func (m *Manager) processPostgresClients() { dbDemand, userDemand, permsDemand, secretsDemand := m.state.GetDemand() - dbs := newConsolidator() + clusters := newConsolidator() for _, db := range dbDemand.ToAdd.List() { - dbs.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) + clusters.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) } for _, db := range dbDemand.ToRemove.List() { - dbs.add(db.Cluster.Name, db.Cluster.Namespace) + clusters.add(db.Cluster.Name, db.Cluster.Namespace) } for _, user := range userDemand.ToAdd.List() { - dbs.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) + clusters.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) } for _, user := range userDemand.ToRemove.List() { - dbs.add(user.Cluster.Name, user.Cluster.Namespace) + clusters.add(user.Cluster.Name, user.Cluster.Namespace) } for _, perm := range permsDemand.ToAdd.List() { - dbs.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) + clusters.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) } for _, perm := range permsDemand.ToRemove.List() { - dbs.add(perm.Cluster.Name, perm.Cluster.Namespace) + clusters.add(perm.Cluster.Name, perm.Cluster.Namespace) } for _, secret := range secretsDemand.ToRemove.List() { @@ -197,19 +175,26 @@ func (m *Manager) processPostgresClients() { } } - for _, namespace := range dbs.getNamespaces() { - for _, db := range dbs.getNames(namespace) { - cli, err := database.New(db, namespace, "postgres") + for _, namespace := range clusters.getNamespaces() { + for _, cluster := range clusters.getNames(namespace) { + cli, err := database.New(cluster, namespace, "postgres", "") if err != nil { - log.Error().Err(err).Msgf("Failed to create database client for %s", db) + log.Error().Err(err).Msgf("Failed to create database client for %s", cluster) continue } defer cli.Stop() for _, perm := range permsDemand.ToRemove.List() { - if perm.Cluster.Name != db || perm.Cluster.Namespace != namespace { + if perm.Cluster.Name != cluster || perm.Cluster.Namespace != namespace { + continue + } + + cli, err := database.New(perm.Cluster.Name, perm.Cluster.Namespace, "postgres", perm.Database) + if err != nil { + log.Error().Err(err).Msgf("Failed to create database client for %s/%s", perm.Cluster.Namespace, perm.Cluster.Name) continue } + defer cli.Stop() log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) err = cli.RevokePermission(perm) @@ -219,7 +204,7 @@ func (m *Manager) processPostgresClients() { } for _, toRemove := range dbDemand.ToRemove.List() { - if toRemove.Cluster.Name != db || toRemove.Cluster.Namespace != namespace { + if toRemove.Cluster.Name != cluster || toRemove.Cluster.Namespace != namespace { continue } @@ -231,7 +216,7 @@ func (m *Manager) processPostgresClients() { } for _, user := range userDemand.ToRemove.List() { - if user.Cluster.Name != db || user.Cluster.Namespace != namespace { + if user.Cluster.Name != cluster || user.Cluster.Namespace != namespace { continue } @@ -243,7 +228,7 @@ func (m *Manager) processPostgresClients() { } for _, toAdd := range dbDemand.ToAdd.List() { - if toAdd.Target.Cluster.Name != db || toAdd.Target.Cluster.Namespace != namespace { + if toAdd.Target.Cluster.Name != cluster || toAdd.Target.Cluster.Namespace != namespace { continue } @@ -255,7 +240,7 @@ func (m *Manager) processPostgresClients() { } for _, user := range userDemand.ToAdd.List() { - if user.Target.Cluster.Name != db || user.Target.Cluster.Namespace != namespace { + if user.Target.Cluster.Name != cluster || user.Target.Cluster.Namespace != namespace { continue } @@ -268,14 +253,23 @@ func (m *Manager) processPostgresClients() { } for _, perm := range permsDemand.ToAdd.List() { - if perm.Target.Cluster.Name != db || perm.Target.Cluster.Namespace != namespace { + perm := perm.Target + + if perm.Cluster.Name != cluster || perm.Cluster.Namespace != namespace { + continue + } + + cli, err := database.New(perm.Cluster.Name, perm.Cluster.Namespace, "postgres", perm.Database) + if err != nil { + log.Error().Err(err).Msgf("Failed to create database client for %s/%s", perm.Cluster.Namespace, perm.Cluster.Name) continue } + defer cli.Stop() - log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) - err := cli.GrantPermission(perm.Target) + log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) + err = cli.GrantPermission(perm) if err != nil { - log.Error().Err(err).Msgf("Failed to grant permission for user %s in database %s of db %s", perm.Target.User, perm.Target.Database, perm.Target.Cluster) + log.Error().Err(err).Msgf("Failed to grant permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) } } } diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 46e7719..13e50e3 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -247,20 +247,19 @@ func (s *State) GetDemand() ( return dbDemand, userDemand, permissionDemand, secretsDemand } -func (s *State) GetActiveClients() []clients.Resource { - clients := []clients.Resource{} +func (s *State) GetActiveClusters() []database.Cluster { + clusters := []database.Cluster{} - for _, client := range s.clients.List() { - target := client.GetTarget() - targetNamespace := client.GetTargetNamespace() - statefulSet, hasSS := s.statefulSets.Get(target, targetNamespace) - - if !hasSS || !statefulSet.IsReady() { + for _, ss := range s.statefulSets.List() { + if !ss.IsReady() { continue } - clients = append(clients, client) + clusters = append(clusters, database.Cluster{ + Name: ss.Name, + Namespace: ss.Namespace, + }) } - return clients + return clusters } diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 7e09189..60b8274 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -88,7 +88,7 @@ func (d *AdminConn) DropUser(username string) error { } func (d *AdminConn) ListDatabases() ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SHOW DATABASES") + rows, err := d.conn.Query(context.Background(), "SELECT datname FROM pg_catalog.pg_database") if err != nil { return nil, fmt.Errorf("failed to list databases: %+v", err) } @@ -97,7 +97,7 @@ func (d *AdminConn) ListDatabases() ([]string, error) { databases := []string{} for rows.Next() { var name string - if err := rows.Scan(&name, nil, nil, nil, nil, nil); err != nil { + if err := rows.Scan(&name); err != nil { return nil, fmt.Errorf("failed to decode database: %+v", err) } @@ -130,7 +130,7 @@ func (d *AdminConn) DropDatabase(database string) error { } func (d *AdminConn) ListPermitted(database string) ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SHOW GRANTS ON DATABASE "+sanitize(database)) + rows, err := d.conn.Query(context.Background(), "SELECT * FROM information_schema.role_table_grants WHERE grantee = '"+sanitize(database)+"'") if err != nil { return nil, fmt.Errorf("failed to list permissions: %+v", err) } @@ -159,22 +159,22 @@ func (d *AdminConn) ListPermitted(database string) ([]string, error) { } func (d *AdminConn) GrantPermissions(username string, database string) error { - query := fmt.Sprintf("GRANT ALL ON DATABASE %s TO %s", sanitize(database), sanitize(username)) + query := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", sanitize(database), sanitize(username)) if _, err := d.conn.Exec(context.Background(), query); err != nil { return fmt.Errorf("failed to grant permissions: %+v", err) } - query = fmt.Sprintf("USE %s; ALTER DEFAULT PRIVILEGES FOR ALL ROLES GRANT ALL ON TABLES TO %s;", sanitize(database), sanitize(username)) - _, err := d.conn.Exec(context.Background(), query) - if pgerr := parsePGXError(err); pgerr != nil { - return fmt.Errorf("failed to grant default table permissions: %+v", pgerr) - } + // query = fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT ALL ON TABLES TO %s;", sanitize(username), sanitize(username)) + // _, err := d.conn.Exec(context.Background(), query) + // if pgerr := parsePGXError(err); pgerr != nil { + // return fmt.Errorf("failed to grant default table permissions: %+v", pgerr) + // } - query = fmt.Sprintf("GRANT ALL ON %s.* TO %s", sanitize(database), sanitize(username)) - _, err = d.conn.Exec(context.Background(), query) - if pgerr := parsePGXError(err); pgerr != nil { - return fmt.Errorf("failed to grant existing table permissions: %+v", err) - } + // query = fmt.Sprintf("GRANT ALL ON %s.* TO %s", sanitize(database), sanitize(username)) + // _, err = d.conn.Exec(context.Background(), query) + // if pgerr := parsePGXError(err); pgerr != nil { + // return fmt.Errorf("failed to grant existing table permissions: %+v", err) + // } log.Info().Msgf("Granted '%s' permission to read/write to '%s'", username, database) From 41d5973d5ca3dde89c2e1d1e461cf1d4c254d196 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sun, 19 May 2024 22:46:43 +0100 Subject: [PATCH 10/22] get password from secret too --- tests/postgres_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/postgres_test.go b/tests/postgres_test.go index d537f30..2baa308 100644 --- a/tests/postgres_test.go +++ b/tests/postgres_test.go @@ -80,6 +80,7 @@ func TestPostgresIntegration(t *testing.T) { Host: secret.GetHost(), Port: int(port), Username: secret.User, + Password: secret.Password, Database: secret.Database, }) mustPass(t, err) From 370baf831ec32f3f84fedacc1be0caff8f252a90 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 20 May 2024 09:28:32 +0100 Subject: [PATCH 11/22] bug in permissions demand, and working on postgres user admin --- deploy/chart/crds/postgres-client.crd.yaml | 2 + internal/dbs/postgres/database/client.go | 7 ++- internal/dbs/postgres/database/types.go | 2 + internal/dbs/postgres/k8s/clients/clients.go | 9 ++++ .../dbs/postgres/managers/database/manager.go | 51 ++++++++++++++----- .../dbs/postgres/managers/database/state.go | 30 +++++++---- pkg/k8s_generic/utils.go | 10 +++- pkg/postgres/admin.go | 28 ++++++++++ tests/postgres_test.go | 1 + 9 files changed, 114 insertions(+), 26 deletions(-) diff --git a/deploy/chart/crds/postgres-client.crd.yaml b/deploy/chart/crds/postgres-client.crd.yaml index f36ef07..418bfaa 100644 --- a/deploy/chart/crds/postgres-client.crd.yaml +++ b/deploy/chart/crds/postgres-client.crd.yaml @@ -25,6 +25,8 @@ spec: required: [ name, namespace ] database: type: string + owner: + type: boolean username: type: string secret: diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index 1125c38..b903e5a 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -74,6 +74,11 @@ func (c *Client) CreateDB(db Database) error { return fmt.Errorf("failed to create database %s: %+v", db.Name, err) } + err = c.conn.SetOwner(db.Name, db.Owner) + if err != nil { + return fmt.Errorf("failed to set owner of database %s to %s: %+v", db.Name, db.Owner, err) + } + return nil } @@ -133,12 +138,12 @@ func (c *Client) DeleteUser(user User) error { } func (c *Client) ListPermitted(db Database) ([]Permission, error) { + permissions := []Permission{} permitted, err := c.conn.ListPermitted(db.Name) if err != nil { return nil, fmt.Errorf("failed to list permissions: %+v", err) } - permissions := []Permission{} for _, user := range permitted { if isReservedUser(user) { continue diff --git a/internal/dbs/postgres/database/types.go b/internal/dbs/postgres/database/types.go index 8970624..9c85f4b 100644 --- a/internal/dbs/postgres/database/types.go +++ b/internal/dbs/postgres/database/types.go @@ -9,6 +9,7 @@ type Cluster struct { type Database struct { Name string + Owner string Cluster Cluster } @@ -38,6 +39,7 @@ type Permission struct { User string Database string Cluster Cluster + Owner bool } func (u Permission) GetName() string { diff --git a/internal/dbs/postgres/k8s/clients/clients.go b/internal/dbs/postgres/k8s/clients/clients.go index 4faa139..3d14e97 100644 --- a/internal/dbs/postgres/k8s/clients/clients.go +++ b/internal/dbs/postgres/k8s/clients/clients.go @@ -30,6 +30,7 @@ type Comparable struct { Database string Username string Secret string + Owner bool } type Resource struct { @@ -55,6 +56,7 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { "database": r.Database, "username": r.Username, "secret": r.Secret, + "owner": r.Owner, }, }) @@ -94,6 +96,13 @@ func fromUnstructured(obj *unstructured.Unstructured) (Resource, error) { return r, fmt.Errorf("failed to get secret: %+v", err) } + r.Owner, err = k8s_generic.GetProperty[bool](obj, "spec", "owner") + if err != nil { + if _, ok := err.(*k8s_generic.MissingError); !ok { + return r, fmt.Errorf("failed to get owner: %+v", err) + } + } + return r, nil } diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index c32ee2b..695d095 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -149,21 +149,45 @@ func (m *Manager) processPostgresClients() { clusters := newConsolidator() for _, db := range dbDemand.ToAdd.List() { + if db.Target.Cluster.Name == "" { + log.Error().Msgf("Database to add %s has no cluster", db.Target.Name) + continue + } clusters.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) } for _, db := range dbDemand.ToRemove.List() { + if db.Cluster.Name == "" { + log.Error().Msgf("Database to remove %s has no cluster", db.Name) + continue + } clusters.add(db.Cluster.Name, db.Cluster.Namespace) } for _, user := range userDemand.ToAdd.List() { + if user.Target.Cluster.Name == "" { + log.Error().Msgf("User to add %s has no cluster", user.Target.Name) + continue + } clusters.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) } for _, user := range userDemand.ToRemove.List() { + if user.Cluster.Name == "" { + log.Error().Msgf("User to remove %s has no cluster", user.Name) + continue + } clusters.add(user.Cluster.Name, user.Cluster.Namespace) } for _, perm := range permsDemand.ToAdd.List() { + if perm.Target.Cluster.Name == "" { + log.Error().Msgf("Permission to add %s has no cluster", perm.Target.User) + continue + } clusters.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) } for _, perm := range permsDemand.ToRemove.List() { + if perm.Cluster.Name == "" { + log.Error().Msgf("Permission to remove %s has no cluster", perm.User) + continue + } clusters.add(perm.Cluster.Name, perm.Cluster.Namespace) } @@ -177,6 +201,8 @@ func (m *Manager) processPostgresClients() { for _, namespace := range clusters.getNamespaces() { for _, cluster := range clusters.getNames(namespace) { + log.Info().Msgf("Processing cluster %s/%s", namespace, cluster) + cli, err := database.New(cluster, namespace, "postgres", "") if err != nil { log.Error().Err(err).Msgf("Failed to create database client for %s", cluster) @@ -189,6 +215,7 @@ func (m *Manager) processPostgresClients() { continue } + log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) cli, err := database.New(perm.Cluster.Name, perm.Cluster.Namespace, "postgres", perm.Database) if err != nil { log.Error().Err(err).Msgf("Failed to create database client for %s/%s", perm.Cluster.Namespace, perm.Cluster.Name) @@ -196,7 +223,6 @@ func (m *Manager) processPostgresClients() { } defer cli.Stop() - log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) err = cli.RevokePermission(perm) if err != nil { log.Error().Err(err).Msgf("Failed to revoke permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) @@ -227,28 +253,27 @@ func (m *Manager) processPostgresClients() { } } - for _, toAdd := range dbDemand.ToAdd.List() { - if toAdd.Target.Cluster.Name != cluster || toAdd.Target.Cluster.Namespace != namespace { + for _, user := range userDemand.ToAdd.List() { + if user.Target.Cluster.Name != cluster || user.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Creating database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) - err := cli.CreateDB(toAdd.Target) + log.Info().Msgf("Creating user %s in db %s", user.Target.Name, user.Target.Cluster) + err := cli.CreateUser(user.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to create database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) + log.Error().Err(err).Msgf("Failed to create user %s in db %s", user.Target.Name, user.Target.Cluster) } } - for _, user := range userDemand.ToAdd.List() { - if user.Target.Cluster.Name != cluster || user.Target.Cluster.Namespace != namespace { + for _, toAdd := range dbDemand.ToAdd.List() { + if toAdd.Target.Cluster.Name != cluster || toAdd.Target.Cluster.Namespace != namespace { continue } - log.Info().Msgf("Creating user %s in db %s", user.Target.Name, user.Target.Cluster) - - err := cli.CreateUser(user.Target) + log.Info().Msgf("Creating database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) + err := cli.CreateDB(toAdd.Target) if err != nil { - log.Error().Err(err).Msgf("Failed to create user %s in db %s", user.Target.Name, user.Target.Cluster) + log.Error().Err(err).Msgf("Failed to create database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) } } @@ -259,6 +284,7 @@ func (m *Manager) processPostgresClients() { continue } + log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) cli, err := database.New(perm.Cluster.Name, perm.Cluster.Namespace, "postgres", perm.Database) if err != nil { log.Error().Err(err).Msgf("Failed to create database client for %s/%s", perm.Cluster.Namespace, perm.Cluster.Name) @@ -266,7 +292,6 @@ func (m *Manager) processPostgresClients() { } defer cli.Stop() - log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) err = cli.GrantPermission(perm) if err != nil { log.Error().Err(err).Msgf("Failed to grant permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 13e50e3..9eebce7 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -67,16 +67,19 @@ func (s *State) getRequests() ( continue } - databaseRequests.Add(state.DemandTarget[clients.Resource, database.Database]{ - Parent: client, - Target: database.Database{ - Name: client.Database, - Cluster: database.Cluster{ - Name: client.Cluster.Name, - Namespace: client.Cluster.Namespace, + if client.Owner { + databaseRequests.Add(state.DemandTarget[clients.Resource, database.Database]{ + Parent: client, + Target: database.Database{ + Name: client.Database, + Cluster: database.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, + }, + Owner: client.Username, }, - }, - }) + }) + } userRequests.Add(state.DemandTarget[clients.Resource, database.User]{ Parent: client, @@ -94,6 +97,7 @@ func (s *State) getRequests() ( Target: database.Permission{ User: client.Username, Database: client.Database, + Owner: client.Owner, Cluster: database.Cluster{ Name: client.Cluster.Name, Namespace: client.Cluster.Namespace, @@ -173,16 +177,20 @@ func (s *State) diffPermissions( demand := state.NewDemand[clients.Resource, database.Permission]() for _, permissionRequest := range requests.List() { - _, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) + existing, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) _, isRefreshing := deadUsers.Get(permissionRequest.Parent.Username, permissionRequest.Target.GetNamespace()) if !permissionExists { demand.ToAdd.Add(permissionRequest) + continue } - if permissionExists && isRefreshing { + if isRefreshing { demand.ToRemove.Add(permissionRequest.Target) demand.ToAdd.Add(permissionRequest) + } else if existing.Owner != permissionRequest.Target.Owner { + demand.ToRemove.Add(existing) + demand.ToAdd.Add(permissionRequest) } } diff --git a/pkg/k8s_generic/utils.go b/pkg/k8s_generic/utils.go index 1de25e8..814cf55 100644 --- a/pkg/k8s_generic/utils.go +++ b/pkg/k8s_generic/utils.go @@ -27,6 +27,14 @@ func GetEncodedProperty(u *unstructured.Unstructured, args ...string) (string, e return string(decoded), nil } +type MissingError struct { + message string +} + +func (m *MissingError) Error() string { + return m.message +} + func GetProperty[T BasicType](u *unstructured.Unstructured, args ...string) (T, error) { var current interface{} = u.Object var empty T @@ -36,7 +44,7 @@ func GetProperty[T BasicType](u *unstructured.Unstructured, args ...string) (T, case map[string]interface{}: value, ok := c[arg] if !ok { - return empty, fmt.Errorf("object doesn't have property %s in %s", arg, strings.Join(args, ".")) + return empty, &MissingError{message: fmt.Sprintf("object doesn't have property %s in %s", arg, strings.Join(args, "."))} } current = value diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 60b8274..0994f9c 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -129,6 +129,34 @@ func (d *AdminConn) DropDatabase(database string) error { return nil } +func (d *AdminConn) SetOwner(database string, user string) error { + log.Info().Msgf("Setting owner of %s to %s", database, user) + if _, err := d.conn.Exec(context.Background(), "ALTER DATABASE "+sanitize(database)+" OWNER TO "+sanitize(user)); err != nil { + return fmt.Errorf("failed to set database owner: %+v", err) + } + + return nil +} + +func (d *AdminConn) GetOwner(database string) (string, error) { + rows, err := d.conn.Query(context.Background(), "SELECT datdba FROM pg_catalog.pg_database WHERE datname = '"+sanitize(database)+"'") + if err != nil { + return "", fmt.Errorf("failed to get database owner: %+v", err) + } + defer rows.Close() + + if !rows.Next() { + return "", fmt.Errorf("database not found") + } + + var owner string + if err := rows.Scan(&owner); err != nil { + return "", fmt.Errorf("failed to decode database owner: %+v", err) + } + + return owner, nil +} + func (d *AdminConn) ListPermitted(database string) ([]string, error) { rows, err := d.conn.Query(context.Background(), "SELECT * FROM information_schema.role_table_grants WHERE grantee = '"+sanitize(database)+"'") if err != nil { diff --git a/tests/postgres_test.go b/tests/postgres_test.go index 2baa308..cb809e2 100644 --- a/tests/postgres_test.go +++ b/tests/postgres_test.go @@ -66,6 +66,7 @@ func TestPostgresIntegration(t *testing.T) { Namespace: namespace, Username: "my_user", Secret: "my-secret", + Owner: true, }, })) From ca811f2edefaeb554a07caf12da5be1a1c513e39 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 20 May 2024 12:57:51 +0100 Subject: [PATCH 12/22] rationalising the logging a bit --- internal/dbs/nats/manager/manager.go | 4 +-- internal/dbs/postgres/database/client.go | 32 ++++++++++++++++++- .../dbs/postgres/managers/database/manager.go | 16 +++------- .../postgres/managers/deployment/manager.go | 4 +-- internal/dbs/redis/manager/manager.go | 4 +-- internal/state/bucket/bucket.go | 4 +-- pkg/k8s_generic/client.go | 7 ++++ pkg/postgres/admin.go | 27 ---------------- pkg/postgres/config.go | 3 +- pkg/postgres/connect.go | 4 +-- 10 files changed, 53 insertions(+), 52 deletions(-) diff --git a/internal/dbs/nats/manager/manager.go b/internal/dbs/nats/manager/manager.go index d1d6d65..63c77b7 100644 --- a/internal/dbs/nats/manager/manager.go +++ b/internal/dbs/nats/manager/manager.go @@ -94,10 +94,10 @@ func (m *Manager) refresh() { m.state.Apply(update) m.debouncer.Trigger() case <-m.debouncer.Wait(): - log.Info().Msg("Processing nats started") + log.Debug().Msg("Processing nats started") m.processNatsDBs() m.processNatsDeployments() - log.Info().Msg("Processing nats finished") + log.Debug().Msg("Processing nats finished") } } diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index b903e5a..020087d 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/benjamin-wright/db-operator/pkg/postgres" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) @@ -12,6 +13,8 @@ type Client struct { conn *postgres.AdminConn cluster string namespace string + database string + logger zerolog.Logger } func New(cluster string, namespace string, password string, database string) (*Client, error) { @@ -23,6 +26,19 @@ func New(cluster string, namespace string, password string, database string) (*C Database: database, } + logger := log.With(). + Str("kind", "postgres"). + Str("cluster", cluster). + Str("namespace", namespace). + Str("database", database). + Logger() + + if database != "" { + logger.Debug().Msgf("Opening connection to Postgres Database %s:%s", cluster, database) + } else { + logger.Debug().Msgf("Opening connection to Postgres Cluster %s", cluster) + } + conn, err := postgres.NewAdminConn(cfg) if err != nil { return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", cluster, err) @@ -32,11 +48,18 @@ func New(cluster string, namespace string, password string, database string) (*C conn: conn, cluster: cluster, namespace: namespace, + database: database, + logger: logger, }, nil } func (c *Client) Stop() { - log.Info().Msgf("Closing connection to DB %s", c.cluster) + if c.database != "" { + c.logger.Debug().Msgf("Closing connection to Postgres Database %s:%s", c.cluster, c.database) + } else { + c.logger.Debug().Msgf("Closing connection to Postgres Cluster %s", c.cluster) + } + c.conn.Stop() } @@ -69,6 +92,8 @@ func (c *Client) ListDBs() ([]Database, error) { } func (c *Client) CreateDB(db Database) error { + c.logger.Info().Msgf("Creating database %s", db.Name) + err := c.conn.CreateDatabase(db.Name) if err != nil { return fmt.Errorf("failed to create database %s: %+v", db.Name, err) @@ -83,6 +108,7 @@ func (c *Client) CreateDB(db Database) error { } func (c *Client) DeleteDB(db Database) error { + c.logger.Info().Msgf("Deleting database %s", db.Name) err := c.conn.DropDatabase(db.Name) if err != nil { return fmt.Errorf("failed to create database %s: %+v", db.Name, err) @@ -120,6 +146,7 @@ func (c *Client) ListUsers() ([]User, error) { } func (c *Client) CreateUser(user User) error { + c.logger.Info().Msgf("Creating user %s", user.Name) err := c.conn.CreateUser(user.Name, user.Password) if err != nil { return fmt.Errorf("failed to create user %s: %+v", user, err) @@ -129,6 +156,7 @@ func (c *Client) CreateUser(user User) error { } func (c *Client) DeleteUser(user User) error { + c.logger.Info().Msgf("Deleting user %s", user.Name) err := c.conn.DropUser(user.Name) if err != nil { return fmt.Errorf("failed to delete user %s: %+v", user, err) @@ -163,6 +191,7 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { } func (c *Client) GrantPermission(permission Permission) error { + c.logger.Info().Msgf("Granting '%s' permission to read/write to '%s'", permission.User, permission.Database) err := c.conn.GrantPermissions(permission.User, permission.Database) if err != nil { return fmt.Errorf("failed to grant permission: %+v", err) @@ -172,6 +201,7 @@ func (c *Client) GrantPermission(permission Permission) error { } func (c *Client) RevokePermission(permission Permission) error { + c.logger.Info().Msgf("Revoking '%s' permission to read/write to '%s'", permission.User, permission.Database) err := c.conn.RevokePermissions(permission.User, permission.Database) if err != nil { return fmt.Errorf("failed to revoke permission: %+v", err) diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index 695d095..c02e044 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -93,11 +93,11 @@ func (m *Manager) refresh() { m.state.Apply(update) m.debouncer.Trigger() case <-m.debouncer.Wait(): - log.Info().Msg("Updating postgres database state") + log.Debug().Msg("Updating postgres database state") m.refreshDatabaseState() - log.Info().Msg("Processing postgres databases started") + log.Debug().Msg("Processing postgres databases started") m.processPostgresClients() - log.Info().Msg("Processing postgres databases finished") + log.Debug().Msg("Processing postgres databases finished") } } @@ -192,7 +192,6 @@ func (m *Manager) processPostgresClients() { } for _, secret := range secretsDemand.ToRemove.List() { - log.Info().Msgf("Removing secret %s", secret.Name) err := m.client.Secrets().Delete(m.ctx, secret.Name, secret.Namespace) if err != nil { log.Error().Err(err).Msgf("Failed to delete secret %s", secret.Name) @@ -201,7 +200,7 @@ func (m *Manager) processPostgresClients() { for _, namespace := range clusters.getNamespaces() { for _, cluster := range clusters.getNames(namespace) { - log.Info().Msgf("Processing cluster %s/%s", namespace, cluster) + log.Debug().Msgf("Processing cluster %s/%s", namespace, cluster) cli, err := database.New(cluster, namespace, "postgres", "") if err != nil { @@ -215,7 +214,6 @@ func (m *Manager) processPostgresClients() { continue } - log.Info().Msgf("Dropping permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) cli, err := database.New(perm.Cluster.Name, perm.Cluster.Namespace, "postgres", perm.Database) if err != nil { log.Error().Err(err).Msgf("Failed to create database client for %s/%s", perm.Cluster.Namespace, perm.Cluster.Name) @@ -234,7 +232,6 @@ func (m *Manager) processPostgresClients() { continue } - log.Info().Msgf("Dropping database %s in db %s", toRemove.Name, toRemove.Cluster) err = cli.DeleteDB(toRemove) if err != nil { log.Error().Err(err).Msgf("Failed to delete database %s in db %s", toRemove.Name, toRemove.Cluster) @@ -246,7 +243,6 @@ func (m *Manager) processPostgresClients() { continue } - log.Info().Msgf("Dropping user %s in db %s", user.Name, user.Cluster) err = cli.DeleteUser(user) if err != nil { log.Error().Err(err).Msgf("Failed to delete user %s in db %s", user.Name, user.Cluster) @@ -258,7 +254,6 @@ func (m *Manager) processPostgresClients() { continue } - log.Info().Msgf("Creating user %s in db %s", user.Target.Name, user.Target.Cluster) err := cli.CreateUser(user.Target) if err != nil { log.Error().Err(err).Msgf("Failed to create user %s in db %s", user.Target.Name, user.Target.Cluster) @@ -270,7 +265,6 @@ func (m *Manager) processPostgresClients() { continue } - log.Info().Msgf("Creating database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) err := cli.CreateDB(toAdd.Target) if err != nil { log.Error().Err(err).Msgf("Failed to create database %s in db %s", toAdd.Target.Name, toAdd.Target.Cluster) @@ -284,7 +278,6 @@ func (m *Manager) processPostgresClients() { continue } - log.Info().Msgf("Adding permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) cli, err := database.New(perm.Cluster.Name, perm.Cluster.Namespace, "postgres", perm.Database) if err != nil { log.Error().Err(err).Msgf("Failed to create database client for %s/%s", perm.Cluster.Namespace, perm.Cluster.Name) @@ -301,7 +294,6 @@ func (m *Manager) processPostgresClients() { } for _, secret := range secretsDemand.ToAdd.List() { - log.Info().Msgf("Adding secret %s", secret.Target.Name) err := m.client.Secrets().Create(m.ctx, secret.Target) if err != nil { log.Error().Err(err).Msgf("Failed to create secret %s", secret.Target.Name) diff --git a/internal/dbs/postgres/managers/deployment/manager.go b/internal/dbs/postgres/managers/deployment/manager.go index c828583..ad7d61c 100644 --- a/internal/dbs/postgres/managers/deployment/manager.go +++ b/internal/dbs/postgres/managers/deployment/manager.go @@ -94,9 +94,9 @@ func (m *Manager) refresh() { m.state.Apply(update) m.debouncer.Trigger() case <-m.debouncer.Wait(): - log.Info().Msg("Processing postgres deployments started") + log.Debug().Msg("Processing postgres deployments started") m.processPostgresDBs() - log.Info().Msg("Processing postgres deployments finished") + log.Debug().Msg("Processing postgres deployments finished") } } diff --git a/internal/dbs/redis/manager/manager.go b/internal/dbs/redis/manager/manager.go index 0403c1f..af03347 100644 --- a/internal/dbs/redis/manager/manager.go +++ b/internal/dbs/redis/manager/manager.go @@ -97,10 +97,10 @@ func (m *Manager) refresh() { m.state.Apply(update) m.debouncer.Trigger() case <-m.debouncer.Wait(): - log.Info().Msg("Processing redis started") + log.Debug().Msg("Processing redis started") m.processRedisDBs() m.processRedisStatefulSets() - log.Info().Msg("Processing redis finished") + log.Debug().Msg("Processing redis finished") } } diff --git a/internal/state/bucket/bucket.go b/internal/state/bucket/bucket.go index ab0a5ea..5891349 100644 --- a/internal/state/bucket/bucket.go +++ b/internal/state/bucket/bucket.go @@ -18,12 +18,12 @@ func NewBucket[T types.Nameable]() Bucket[T] { func (b *Bucket[T]) Apply(update k8s_generic.Update[T]) { for _, toRemove := range update.ToRemove { - log.Info().Interface("toRemove", toRemove).Msg("Removing") + log.Debug().Interface("toRemove", toRemove).Msg("Removing") b.Remove(toRemove) } for _, toAdd := range update.ToAdd { - log.Info().Interface("toAdd", toAdd).Msg("Adding") + log.Debug().Interface("toAdd", toAdd).Msg("Adding") b.Add(toAdd) } } diff --git a/pkg/k8s_generic/client.go b/pkg/k8s_generic/client.go index f8013a8..1be3069 100644 --- a/pkg/k8s_generic/client.go +++ b/pkg/k8s_generic/client.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,6 +36,7 @@ type Client[T Resource] struct { kind string labelFilters map[string]string fromUnstructured FromUnstructured[T] + logger zerolog.Logger } type ClientArgs[T Resource] struct { @@ -52,10 +54,12 @@ func NewClient[T Resource](b *Builder, args ClientArgs[T]) *Client[T] { labelFilters: args.LabelFilters, kind: args.Kind, fromUnstructured: args.FromUnstructured, + logger: log.With().Str("kind", args.Kind).Logger(), } } func (c *Client[T]) Create(ctx context.Context, resource T) error { + c.logger.Info().Msgf("Creating %s:%s", resource.GetNamespace(), resource.GetName()) _, err := c.client.Resource(c.schema).Namespace(resource.GetNamespace()).Create(ctx, resource.ToUnstructured(), v1.CreateOptions{}) if err != nil { return fmt.Errorf("failed to create %T: %+v", resource, err) @@ -107,6 +111,7 @@ func (c *Client[T]) GetAll(ctx context.Context) ([]T, error) { } func (c *Client[T]) Delete(ctx context.Context, name string, namespace string) error { + c.logger.Info().Msgf("Deleting %s:%s", namespace, name) err := c.client.Resource(c.schema).Namespace(namespace).Delete(ctx, name, v1.DeleteOptions{}) if err != nil { return fmt.Errorf("failed to delete %T: %+v", name, err) @@ -116,6 +121,7 @@ func (c *Client[T]) Delete(ctx context.Context, name string, namespace string) e } func (c *Client[T]) DeleteAll(ctx context.Context, namespace string) error { + c.logger.Info().Msgf("Deleting all resources in %s", namespace) err := c.client.Resource(c.schema).Namespace(namespace).DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{}) if err != nil { return fmt.Errorf("failed to delete all resources: %+v", err) @@ -125,6 +131,7 @@ func (c *Client[T]) DeleteAll(ctx context.Context, namespace string) error { } func (c *Client[T]) Update(ctx context.Context, resource T) error { + c.logger.Info().Msgf("Updating %s:%s", resource.GetNamespace(), resource.GetName()) _, err := c.client.Resource(c.schema).Namespace(resource.GetNamespace()).Update(ctx, resource.ToUnstructured(), v1.UpdateOptions{}) if err != nil { return fmt.Errorf("failed up update resource %s: %+v", resource.GetName(), err) diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 0994f9c..4133394 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -4,27 +4,9 @@ import ( "context" "fmt" - "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" - "github.com/rs/zerolog/log" ) -func parsePGXError(err error) error { - if err == nil { - return nil - } - - if pgerr, ok := err.(*pgconn.PgError); ok { - if pgerr.Message == "no object matched" { - return nil - } - - return fmt.Errorf("%s [%s]: %s", pgerr.Message, pgerr.Code, pgerr.Detail) - } - - return err -} - type AdminConn struct { conn *pgx.Conn } @@ -64,7 +46,6 @@ func (d *AdminConn) ListUsers() ([]string, error) { } func (d *AdminConn) CreateUser(username string, password string) error { - log.Info().Msgf("Creating user %s", username) if password != "" { if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)+" WITH PASSWORD '"+sanitize(password)+"'"); err != nil { return fmt.Errorf("failed to create database user: %+v", err) @@ -79,7 +60,6 @@ func (d *AdminConn) CreateUser(username string, password string) error { } func (d *AdminConn) DropUser(username string) error { - log.Info().Msgf("Dropping user %s", username) if _, err := d.conn.Exec(context.Background(), "DROP USER "+sanitize(username)); err != nil { return fmt.Errorf("failed to drop database user: %+v", err) } @@ -112,7 +92,6 @@ func sanitize(name string) string { } func (d *AdminConn) CreateDatabase(database string) error { - log.Info().Msgf("Creating database %s", database) if _, err := d.conn.Exec(context.Background(), "CREATE DATABASE "+sanitize(database)); err != nil { return fmt.Errorf("failed to create database: %+v", err) } @@ -121,7 +100,6 @@ func (d *AdminConn) CreateDatabase(database string) error { } func (d *AdminConn) DropDatabase(database string) error { - log.Info().Msgf("Dropping database %s", database) if _, err := d.conn.Exec(context.Background(), "DROP DATABASE "+sanitize(database)); err != nil { return fmt.Errorf("failed to drop database: %+v", err) } @@ -130,7 +108,6 @@ func (d *AdminConn) DropDatabase(database string) error { } func (d *AdminConn) SetOwner(database string, user string) error { - log.Info().Msgf("Setting owner of %s to %s", database, user) if _, err := d.conn.Exec(context.Background(), "ALTER DATABASE "+sanitize(database)+" OWNER TO "+sanitize(user)); err != nil { return fmt.Errorf("failed to set database owner: %+v", err) } @@ -204,8 +181,6 @@ func (d *AdminConn) GrantPermissions(username string, database string) error { // return fmt.Errorf("failed to grant existing table permissions: %+v", err) // } - log.Info().Msgf("Granted '%s' permission to read/write to '%s'", username, database) - return nil } @@ -225,7 +200,5 @@ func (d *AdminConn) RevokePermissions(username string, database string) error { return fmt.Errorf("failed to revoke permissions: %+v", err) } - log.Info().Msgf("Revoked '%s' permission to read/write from '%s'", username, database) - return nil } diff --git a/pkg/postgres/config.go b/pkg/postgres/config.go index 5d153f5..40acd85 100644 --- a/pkg/postgres/config.go +++ b/pkg/postgres/config.go @@ -3,6 +3,7 @@ package postgres import ( "errors" "fmt" + "net/url" "os" "strconv" ) @@ -95,7 +96,7 @@ func (c ConnectConfig) ConnectionString() string { password := "" if c.Password != "" { - password = ":" + c.Password + password = ":" + url.QueryEscape(c.Password) } return fmt.Sprintf("postgresql://%s%s@%s:%d%s", c.Username, password, c.Host, c.Port, dbSuffix) diff --git a/pkg/postgres/connect.go b/pkg/postgres/connect.go index 81d556c..3358c61 100644 --- a/pkg/postgres/connect.go +++ b/pkg/postgres/connect.go @@ -25,7 +25,7 @@ func getConnection(config *pgx.ConnConfig) *pgx.Conn { time.Sleep(time.Second * backoff) backoff = backoff + time.Duration(1) } else { - log.Info().Msg("Connected") + log.Debug().Msg("Connected") break } } @@ -43,8 +43,6 @@ func getConnection(config *pgx.ConnConfig) *pgx.Conn { func Connect(config ConnectConfig) (*pgx.Conn, error) { connectionString := config.ConnectionString() - log.Info().Msgf("Connecting to postgres with connection string: %s", config) - pgxConfig, err := pgx.ParseConfig(connectionString) if err != nil { return nil, err From 08253e04614d24e996a516d76e17aa03cb90b8d3 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 20 May 2024 21:07:30 +0100 Subject: [PATCH 13/22] WIP: PG permissions kinda working --- internal/dbs/postgres/database/client.go | 12 ++- internal/dbs/postgres/database/types.go | 2 +- .../dbs/postgres/managers/database/manager.go | 24 ------ .../dbs/postgres/managers/database/state.go | 79 +++++++++++-------- pkg/postgres/admin.go | 64 +++++++-------- tests/postgres_test.go | 11 +++ 6 files changed, 100 insertions(+), 92 deletions(-) diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index 020087d..d0bb41c 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -79,12 +79,18 @@ func (c *Client) ListDBs() ([]Database, error) { continue } + owner, err := c.conn.GetOwner(name) + if err != nil { + return nil, fmt.Errorf("failed to get owner of database %s: %+v", name, err) + } + databases = append(databases, Database{ Cluster: Cluster{ Name: c.cluster, Namespace: c.namespace, }, - Name: name, + Name: name, + Owner: owner, }) } @@ -192,7 +198,7 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { func (c *Client) GrantPermission(permission Permission) error { c.logger.Info().Msgf("Granting '%s' permission to read/write to '%s'", permission.User, permission.Database) - err := c.conn.GrantPermissions(permission.User, permission.Database) + err := c.conn.GrantPermissions(permission.User, permission.Owner) if err != nil { return fmt.Errorf("failed to grant permission: %+v", err) } @@ -202,7 +208,7 @@ func (c *Client) GrantPermission(permission Permission) error { func (c *Client) RevokePermission(permission Permission) error { c.logger.Info().Msgf("Revoking '%s' permission to read/write to '%s'", permission.User, permission.Database) - err := c.conn.RevokePermissions(permission.User, permission.Database) + err := c.conn.RevokePermissions(permission.User, permission.Owner) if err != nil { return fmt.Errorf("failed to revoke permission: %+v", err) } diff --git a/internal/dbs/postgres/database/types.go b/internal/dbs/postgres/database/types.go index 9c85f4b..25d6e9c 100644 --- a/internal/dbs/postgres/database/types.go +++ b/internal/dbs/postgres/database/types.go @@ -39,7 +39,7 @@ type Permission struct { User string Database string Cluster Cluster - Owner bool + Owner string } func (u Permission) GetName() string { diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index c02e044..3cf74ec 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -149,45 +149,21 @@ func (m *Manager) processPostgresClients() { clusters := newConsolidator() for _, db := range dbDemand.ToAdd.List() { - if db.Target.Cluster.Name == "" { - log.Error().Msgf("Database to add %s has no cluster", db.Target.Name) - continue - } clusters.add(db.Target.Cluster.Name, db.Target.Cluster.Namespace) } for _, db := range dbDemand.ToRemove.List() { - if db.Cluster.Name == "" { - log.Error().Msgf("Database to remove %s has no cluster", db.Name) - continue - } clusters.add(db.Cluster.Name, db.Cluster.Namespace) } for _, user := range userDemand.ToAdd.List() { - if user.Target.Cluster.Name == "" { - log.Error().Msgf("User to add %s has no cluster", user.Target.Name) - continue - } clusters.add(user.Target.Cluster.Name, user.Target.Cluster.Namespace) } for _, user := range userDemand.ToRemove.List() { - if user.Cluster.Name == "" { - log.Error().Msgf("User to remove %s has no cluster", user.Name) - continue - } clusters.add(user.Cluster.Name, user.Cluster.Namespace) } for _, perm := range permsDemand.ToAdd.List() { - if perm.Target.Cluster.Name == "" { - log.Error().Msgf("Permission to add %s has no cluster", perm.Target.User) - continue - } clusters.add(perm.Target.Cluster.Name, perm.Target.Cluster.Namespace) } for _, perm := range permsDemand.ToRemove.List() { - if perm.Cluster.Name == "" { - log.Error().Msgf("Permission to remove %s has no cluster", perm.User) - continue - } clusters.add(perm.Cluster.Name, perm.Cluster.Namespace) } diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 9eebce7..8c48dcb 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -67,6 +67,17 @@ func (s *State) getRequests() ( continue } + userRequests.Add(state.DemandTarget[clients.Resource, database.User]{ + Parent: client, + Target: database.User{ + Name: client.Username, + Cluster: database.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, + }, + }, + }) + if client.Owner { databaseRequests.Add(state.DemandTarget[clients.Resource, database.Database]{ Parent: client, @@ -79,31 +90,19 @@ func (s *State) getRequests() ( Owner: client.Username, }, }) - } - - userRequests.Add(state.DemandTarget[clients.Resource, database.User]{ - Parent: client, - Target: database.User{ - Name: client.Username, - Cluster: database.Cluster{ - Name: client.Cluster.Name, - Namespace: client.Cluster.Namespace, - }, - }, - }) - - permissionRequests.Add(state.DemandTarget[clients.Resource, database.Permission]{ - Parent: client, - Target: database.Permission{ - User: client.Username, - Database: client.Database, - Owner: client.Owner, - Cluster: database.Cluster{ - Name: client.Cluster.Name, - Namespace: client.Cluster.Namespace, + } else { + permissionRequests.Add(state.DemandTarget[clients.Resource, database.Permission]{ + Parent: client, + Target: database.Permission{ + User: client.Username, + Database: client.Database, + Cluster: database.Cluster{ + Name: client.Cluster.Name, + Namespace: client.Cluster.Namespace, + }, }, - }, - }) + }) + } secretRequests.Add(state.DemandTarget[clients.Resource, secrets.Resource]{ Parent: client, @@ -173,6 +172,7 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso func (s *State) diffPermissions( requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], deadUsers bucket.Bucket[database.User], + newDatabases bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]], ) state.Demand[clients.Resource, database.Permission] { demand := state.NewDemand[clients.Resource, database.Permission]() @@ -180,17 +180,30 @@ func (s *State) diffPermissions( existing, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) _, isRefreshing := deadUsers.Get(permissionRequest.Parent.Username, permissionRequest.Target.GetNamespace()) - if !permissionExists { - demand.ToAdd.Add(permissionRequest) - continue + if permissionExists { + if isRefreshing { + demand.ToRemove.Add(permissionRequest.Target) + permissionExists = false + } else if existing.Owner != permissionRequest.Target.Owner { + demand.ToRemove.Add(existing) + permissionExists = false + } } - if isRefreshing { - demand.ToRemove.Add(permissionRequest.Target) - demand.ToAdd.Add(permissionRequest) - } else if existing.Owner != permissionRequest.Target.Owner { - demand.ToRemove.Add(existing) + if !permissionExists { + if database, ok := newDatabases.Get(permissionRequest.Target.Database, permissionRequest.Target.GetNamespace()); ok { + permissionRequest.Target.Owner = database.Target.Owner + } else if database, ok := s.databases.Get(permissionRequest.Target.Database, permissionRequest.Target.GetNamespace()); ok { + permissionRequest.Target.Owner = database.Owner + } else { + log.Logger.Error().Str("permission", permissionRequest.Target.GetName()).Msg("wat dis? Database not found for permission") + continue + } + + log.Info().Msgf("Adding permission %+v", permissionRequest) + demand.ToAdd.Add(permissionRequest) + continue } } @@ -249,7 +262,7 @@ func (s *State) GetDemand() ( dbDemand := s.diffDatabases(dbRequests) userDemand := s.diffUsers(userRequests) - permissionDemand := s.diffPermissions(permissionRequests, userDemand.ToRemove) + permissionDemand := s.diffPermissions(permissionRequests, userDemand.ToRemove, dbDemand.ToAdd) secretsDemand := s.diffSecrets(secretRequests, userDemand) return dbDemand, userDemand, permissionDemand, secretsDemand diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 4133394..434af2b 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -3,6 +3,7 @@ package postgres import ( "context" "fmt" + "regexp" "github.com/jackc/pgx/v4" ) @@ -116,7 +117,7 @@ func (d *AdminConn) SetOwner(database string, user string) error { } func (d *AdminConn) GetOwner(database string) (string, error) { - rows, err := d.conn.Query(context.Background(), "SELECT datdba FROM pg_catalog.pg_database WHERE datname = '"+sanitize(database)+"'") + rows, err := d.conn.Query(context.Background(), "SELECT B.usename FROM pg_database A INNER JOIN pg_user B ON A.datdba = B.usesysid WHERE A.datname = $1", database) if err != nil { return "", fmt.Errorf("failed to get database owner: %+v", err) } @@ -134,8 +135,10 @@ func (d *AdminConn) GetOwner(database string) (string, error) { return owner, nil } +var ACL_REGEX = regexp.MustCompile(`^{(\w+)=.*\/.*}$`) + func (d *AdminConn) ListPermitted(database string) ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SELECT * FROM information_schema.role_table_grants WHERE grantee = '"+sanitize(database)+"'") + rows, err := d.conn.Query(context.Background(), "SELECT defaclacl FROM pg_default_acl") if err != nil { return nil, fmt.Errorf("failed to list permissions: %+v", err) } @@ -144,15 +147,22 @@ func (d *AdminConn) ListPermitted(database string) ([]string, error) { permittedMap := map[string]struct{}{} for rows.Next() { - var user string - var privilege_type string - if err := rows.Scan(nil, &user, &privilege_type, nil); err != nil { + var acl string + if err := rows.Scan(&acl); err != nil { return nil, fmt.Errorf("failed to decode user permission: %+v", err) } - if privilege_type == "ALL" { - permittedMap[user] = struct{}{} + matches := ACL_REGEX.FindStringSubmatch(acl) + if len(matches) != 2 { + return nil, fmt.Errorf("failed to parse user permission: %s", acl) } + + user := matches[1] + if user == "" { + return nil, fmt.Errorf("failed to parse user permission: %s", acl) + } + + permittedMap[user] = struct{}{} } permitted := make([]string, len(permittedMap)) @@ -163,41 +173,33 @@ func (d *AdminConn) ListPermitted(database string) ([]string, error) { return permitted, nil } -func (d *AdminConn) GrantPermissions(username string, database string) error { - query := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", sanitize(database), sanitize(username)) - if _, err := d.conn.Exec(context.Background(), query); err != nil { - return fmt.Errorf("failed to grant permissions: %+v", err) +func (d *AdminConn) GrantPermissions(username string, owner string) error { + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public GRANT INSERT, SELECT, UPDATE, DELETE ON TABLES TO %s", sanitize(owner), sanitize(username))); err != nil { + return fmt.Errorf("failed to grant default table permissions: %+v", err) } - // query = fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT ALL ON TABLES TO %s;", sanitize(username), sanitize(username)) - // _, err := d.conn.Exec(context.Background(), query) - // if pgerr := parsePGXError(err); pgerr != nil { - // return fmt.Errorf("failed to grant default table permissions: %+v", pgerr) - // } + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public GRANT SELECT, UPDATE ON SEQUENCES TO %s", sanitize(owner), sanitize(username))); err != nil { + return fmt.Errorf("failed to grant default sequence permissions: %+v", err) + } - // query = fmt.Sprintf("GRANT ALL ON %s.* TO %s", sanitize(database), sanitize(username)) - // _, err = d.conn.Exec(context.Background(), query) - // if pgerr := parsePGXError(err); pgerr != nil { - // return fmt.Errorf("failed to grant existing table permissions: %+v", err) - // } + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES TO %s", sanitize(username))); err != nil { + return fmt.Errorf("failed to grant existing table permissions: %+v", err) + } return nil } -func (d *AdminConn) RevokePermissions(username string, database string) error { - query := fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR ALL ROLES REVOKE ALL ON DATABASE %s FROM %s;", sanitize(database), sanitize(username)) - if _, err := d.conn.Exec(context.Background(), query); err != nil { - return fmt.Errorf("failed to revoke default permissions: %+v", err) +func (d *AdminConn) RevokePermissions(username string, owner string) error { + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public REVOKE INSERT, SELECT, UPDATE, DELETE ON TABLES FROM %s", sanitize(owner), sanitize(username))); err != nil { + return fmt.Errorf("failed to revoke default table permissions: %+v", err) } - query = fmt.Sprintf("REVOKE ALL ON * FROM %s", sanitize(username)) - if _, err := d.conn.Exec(context.Background(), query); err != nil { - return fmt.Errorf("failed to revoke existing table permissions: %+v", err) + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public REVOKE SELECT, UPDATE ON SEQUENCES FROM %s", sanitize(owner), sanitize(username))); err != nil { + return fmt.Errorf("failed to revoke default sequence permissions: %+v", err) } - query = fmt.Sprintf("REVOKE ALL ON DATABASE %s FROM %s", sanitize(database), sanitize(username)) - if _, err := d.conn.Exec(context.Background(), query); err != nil { - return fmt.Errorf("failed to revoke permissions: %+v", err) + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("REVOKE INSERT, SELECT, UPDATE, DELETE ON ALL TABLES FROM %s", sanitize(username))); err != nil { + return fmt.Errorf("failed to revoke existing table permissions: %+v", err) } return nil diff --git a/tests/postgres_test.go b/tests/postgres_test.go index cb809e2..084b9bd 100644 --- a/tests/postgres_test.go +++ b/tests/postgres_test.go @@ -70,6 +70,17 @@ func TestPostgresIntegration(t *testing.T) { }, })) + mustPass(t, client.Clients().Create(context.Background(), clients.Resource{ + Comparable: clients.Comparable{ + Cluster: clients.Cluster{Name: "different-db", Namespace: namespace}, + Database: "new_db", + Name: "other-client", + Namespace: namespace, + Username: "other_user", + Secret: "other-secret", + }, + })) + secret := waitForResult(t, func() (secrets.Resource, error) { return client.Secrets().Get(context.Background(), "my-secret", namespace) }) From fec883e88eab0db9f297f3d07f67cc5398e9ac15 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 20 May 2024 21:20:02 +0100 Subject: [PATCH 14/22] maybe not working so much --- internal/dbs/postgres/database/client.go | 1 + .../dbs/postgres/managers/database/state.go | 35 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index d0bb41c..cacc4d9 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -190,6 +190,7 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { }, Database: db.Name, User: user, + Owner: db.Owner, }) } diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 8c48dcb..c3c4ac2 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -1,6 +1,8 @@ package database import ( + "fmt" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" @@ -169,6 +171,18 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso return demand } +func (s *State) getOwner(database string, newDatabases bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]]) (string, error) { + if db, ok := newDatabases.Get(database, ""); ok { + return db.Target.Owner, nil + } + + if db, ok := s.databases.Get(database, ""); ok { + return db.Owner, nil + } + + return "", fmt.Errorf("database %s not found", database) +} + func (s *State) diffPermissions( requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], deadUsers bucket.Bucket[database.User], @@ -182,6 +196,13 @@ func (s *State) diffPermissions( if permissionExists { if isRefreshing { + owner, err := s.getOwner(permissionRequest.Target.Database, newDatabases) + if err != nil { + log.Logger.Error().Str("permission", permissionRequest.Target.GetName()).Msg(err.Error()) + continue + } + + permissionRequest.Target.Owner = owner demand.ToRemove.Add(permissionRequest.Target) permissionExists = false } else if existing.Owner != permissionRequest.Target.Owner { @@ -191,17 +212,15 @@ func (s *State) diffPermissions( } if !permissionExists { - if database, ok := newDatabases.Get(permissionRequest.Target.Database, permissionRequest.Target.GetNamespace()); ok { - permissionRequest.Target.Owner = database.Target.Owner - } else if database, ok := s.databases.Get(permissionRequest.Target.Database, permissionRequest.Target.GetNamespace()); ok { - permissionRequest.Target.Owner = database.Owner - } else { - log.Logger.Error().Str("permission", permissionRequest.Target.GetName()).Msg("wat dis? Database not found for permission") + log.Info().Msgf("Adding permission %+v", permissionRequest) + + owner, err := s.getOwner(permissionRequest.Target.Database, newDatabases) + if err != nil { + log.Logger.Error().Str("permission", permissionRequest.Target.GetName()).Msg(err.Error()) continue } - log.Info().Msgf("Adding permission %+v", permissionRequest) - + permissionRequest.Target.Owner = owner demand.ToAdd.Add(permissionRequest) continue } From d000d5ee0cbe85e5dd133a436d6bc6dbfd6ffe21 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 20 May 2024 23:08:56 +0100 Subject: [PATCH 15/22] mostly there, but auth not working --- README.md | 6 ++++- internal/dbs/postgres/database/client.go | 17 +++++++++---- internal/dbs/postgres/database/types.go | 1 - .../dbs/postgres/managers/database/manager.go | 4 ++++ .../dbs/postgres/managers/database/state.go | 24 ++----------------- pkg/postgres/admin.go | 10 +++++--- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b881e8b..c5ff585 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,8 @@ An operator for creating and managing development databases and other stateful i Currently supports: - Postgres - Redis -- NATs \ No newline at end of file +- NATs + +## NOTES + +- use the database client to fetch the owner when setting and unsetting permissions \ No newline at end of file diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index cacc4d9..7cc0d77 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -117,7 +117,7 @@ func (c *Client) DeleteDB(db Database) error { c.logger.Info().Msgf("Deleting database %s", db.Name) err := c.conn.DropDatabase(db.Name) if err != nil { - return fmt.Errorf("failed to create database %s: %+v", db.Name, err) + return fmt.Errorf("failed to delete database %s: %+v", db.Name, err) } return nil @@ -190,7 +190,6 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { }, Database: db.Name, User: user, - Owner: db.Owner, }) } @@ -198,8 +197,13 @@ func (c *Client) ListPermitted(db Database) ([]Permission, error) { } func (c *Client) GrantPermission(permission Permission) error { + owner, err := c.conn.GetOwner(permission.Database) + if err != nil { + return fmt.Errorf("failed to get owner of database %s: %+v", permission.Database, err) + } + c.logger.Info().Msgf("Granting '%s' permission to read/write to '%s'", permission.User, permission.Database) - err := c.conn.GrantPermissions(permission.User, permission.Owner) + err = c.conn.GrantPermissions(permission.User, owner) if err != nil { return fmt.Errorf("failed to grant permission: %+v", err) } @@ -208,8 +212,13 @@ func (c *Client) GrantPermission(permission Permission) error { } func (c *Client) RevokePermission(permission Permission) error { + owner, err := c.conn.GetOwner(permission.Database) + if err != nil { + return fmt.Errorf("failed to get owner of database %s: %+v", permission.Database, err) + } + c.logger.Info().Msgf("Revoking '%s' permission to read/write to '%s'", permission.User, permission.Database) - err := c.conn.RevokePermissions(permission.User, permission.Owner) + err = c.conn.RevokePermissions(permission.User, owner) if err != nil { return fmt.Errorf("failed to revoke permission: %+v", err) } diff --git a/internal/dbs/postgres/database/types.go b/internal/dbs/postgres/database/types.go index 25d6e9c..093d635 100644 --- a/internal/dbs/postgres/database/types.go +++ b/internal/dbs/postgres/database/types.go @@ -39,7 +39,6 @@ type Permission struct { User string Database string Cluster Cluster - Owner string } func (u Permission) GetName() string { diff --git a/internal/dbs/postgres/managers/database/manager.go b/internal/dbs/postgres/managers/database/manager.go index 3cf74ec..1f745a0 100644 --- a/internal/dbs/postgres/managers/database/manager.go +++ b/internal/dbs/postgres/managers/database/manager.go @@ -201,6 +201,8 @@ func (m *Manager) processPostgresClients() { if err != nil { log.Error().Err(err).Msgf("Failed to revoke permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) } + + cli.Stop() } for _, toRemove := range dbDemand.ToRemove.List() { @@ -265,6 +267,8 @@ func (m *Manager) processPostgresClients() { if err != nil { log.Error().Err(err).Msgf("Failed to grant permission for user %s in database %s of db %s", perm.User, perm.Database, perm.Cluster) } + + cli.Stop() } } } diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index c3c4ac2..b7592ef 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -186,41 +186,21 @@ func (s *State) getOwner(database string, newDatabases bucket.Bucket[state.Deman func (s *State) diffPermissions( requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], deadUsers bucket.Bucket[database.User], - newDatabases bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]], ) state.Demand[clients.Resource, database.Permission] { demand := state.NewDemand[clients.Resource, database.Permission]() for _, permissionRequest := range requests.List() { - existing, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) + _, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) _, isRefreshing := deadUsers.Get(permissionRequest.Parent.Username, permissionRequest.Target.GetNamespace()) if permissionExists { if isRefreshing { - owner, err := s.getOwner(permissionRequest.Target.Database, newDatabases) - if err != nil { - log.Logger.Error().Str("permission", permissionRequest.Target.GetName()).Msg(err.Error()) - continue - } - - permissionRequest.Target.Owner = owner demand.ToRemove.Add(permissionRequest.Target) permissionExists = false - } else if existing.Owner != permissionRequest.Target.Owner { - demand.ToRemove.Add(existing) - permissionExists = false } } if !permissionExists { - log.Info().Msgf("Adding permission %+v", permissionRequest) - - owner, err := s.getOwner(permissionRequest.Target.Database, newDatabases) - if err != nil { - log.Logger.Error().Str("permission", permissionRequest.Target.GetName()).Msg(err.Error()) - continue - } - - permissionRequest.Target.Owner = owner demand.ToAdd.Add(permissionRequest) continue } @@ -281,7 +261,7 @@ func (s *State) GetDemand() ( dbDemand := s.diffDatabases(dbRequests) userDemand := s.diffUsers(userRequests) - permissionDemand := s.diffPermissions(permissionRequests, userDemand.ToRemove, dbDemand.ToAdd) + permissionDemand := s.diffPermissions(permissionRequests, userDemand.ToRemove) secretsDemand := s.diffSecrets(secretRequests, userDemand) return dbDemand, userDemand, permissionDemand, secretsDemand diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin.go index 434af2b..d4b8a29 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin.go @@ -61,6 +61,10 @@ func (d *AdminConn) CreateUser(username string, password string) error { } func (d *AdminConn) DropUser(username string) error { + if _, err := d.conn.Exec(context.Background(), "DROP OWNED BY "+sanitize(username)); err != nil { + return fmt.Errorf("failed to revoke user permissions: %+v", err) + } + if _, err := d.conn.Exec(context.Background(), "DROP USER "+sanitize(username)); err != nil { return fmt.Errorf("failed to drop database user: %+v", err) } @@ -101,7 +105,7 @@ func (d *AdminConn) CreateDatabase(database string) error { } func (d *AdminConn) DropDatabase(database string) error { - if _, err := d.conn.Exec(context.Background(), "DROP DATABASE "+sanitize(database)); err != nil { + if _, err := d.conn.Exec(context.Background(), "DROP DATABASE "+sanitize(database)+" WITH (FORCE)"); err != nil { return fmt.Errorf("failed to drop database: %+v", err) } @@ -182,7 +186,7 @@ func (d *AdminConn) GrantPermissions(username string, owner string) error { return fmt.Errorf("failed to grant default sequence permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES TO %s", sanitize(username))); err != nil { + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO %s", sanitize(username))); err != nil { return fmt.Errorf("failed to grant existing table permissions: %+v", err) } @@ -198,7 +202,7 @@ func (d *AdminConn) RevokePermissions(username string, owner string) error { return fmt.Errorf("failed to revoke default sequence permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("REVOKE INSERT, SELECT, UPDATE, DELETE ON ALL TABLES FROM %s", sanitize(username))); err != nil { + if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("REVOKE INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM %s", sanitize(username))); err != nil { return fmt.Errorf("failed to revoke existing table permissions: %+v", err) } From 3874be98bc23d3737b569aeae48a49741f0738c1 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Tue, 21 May 2024 18:01:49 +0100 Subject: [PATCH 16/22] adding unit tests for the diffing stuff that's hard to debug --- .../dbs/postgres/managers/database/state.go | 4 +- .../postgres/managers/database/state_test.go | 226 ++++++++++++++++++ internal/state/demand.go | 18 ++ 3 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 internal/dbs/postgres/managers/database/state_test.go diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index b7592ef..0d10f24 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -144,6 +144,8 @@ func (s *State) diffDatabases(requests bucket.Bucket[state.DemandTarget[clients. return demand } +var generatePassword = utils.GeneratePassword + func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.User]]) state.Demand[clients.Resource, database.User] { demand := state.NewDemand[clients.Resource, database.User]() @@ -157,7 +159,7 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso } if !userExists { - userRequest.Target.Password = utils.GeneratePassword(32, true, true) + userRequest.Target.Password = generatePassword(32, true, true) demand.ToAdd.Add(userRequest) } } diff --git a/internal/dbs/postgres/managers/database/state_test.go b/internal/dbs/postgres/managers/database/state_test.go new file mode 100644 index 0000000..245638e --- /dev/null +++ b/internal/dbs/postgres/managers/database/state_test.go @@ -0,0 +1,226 @@ +package database + +import ( + "strconv" + "testing" + + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" + "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/stateful_sets" + "github.com/benjamin-wright/db-operator/internal/state" + "github.com/benjamin-wright/db-operator/internal/state/bucket" + "github.com/stretchr/testify/assert" +) + +func client(id int, clusterid int, owner bool) clients.Resource { + return clients.Resource{ + Comparable: clients.Comparable{ + Name: "client" + strconv.Itoa(id), + Namespace: "namespace" + strconv.Itoa(id), + Cluster: clients.Cluster{ + Name: "cluster" + strconv.Itoa(clusterid), + Namespace: "cluster-namespace" + strconv.Itoa(clusterid), + }, + Database: "database" + strconv.Itoa(clusterid), + Username: "user" + strconv.Itoa(id), + Secret: "secret" + strconv.Itoa(id), + Owner: owner, + }, + } +} + +func statefulset(clusterid int, ready bool) stateful_sets.Resource { + return stateful_sets.Resource{ + Comparable: stateful_sets.Comparable{ + Name: "cluster" + strconv.Itoa(clusterid), + Namespace: "cluster-namespace" + strconv.Itoa(clusterid), + Storage: "storage" + strconv.Itoa(clusterid), + Ready: ready, + }, + } +} + +func secret(id int, clusterid int, password string) secrets.Resource { + return secrets.Resource{ + Comparable: secrets.Comparable{ + Name: "secret" + strconv.Itoa(id), + Namespace: "namespace" + strconv.Itoa(id), + Cluster: secrets.Cluster{ + Name: "cluster" + strconv.Itoa(clusterid), + Namespace: "cluster-namespace" + strconv.Itoa(clusterid), + }, + Database: "database" + strconv.Itoa(clusterid), + User: "user" + strconv.Itoa(id), + Password: password, + }, + } +} + +func db(ownerid int, clusterid int) database.Database { + return database.Database{ + Name: "database" + strconv.Itoa(clusterid), + Owner: "user" + strconv.Itoa(ownerid), + Cluster: database.Cluster{ + Name: "cluster" + strconv.Itoa(clusterid), + Namespace: "cluster-namespace" + strconv.Itoa(clusterid), + }, + } +} + +func user(id int, clusterid int, password string) database.User { + return database.User{ + Name: "user" + strconv.Itoa(id), + Password: password, + Cluster: database.Cluster{ + Name: "cluster" + strconv.Itoa(clusterid), + Namespace: "cluster-namespace" + strconv.Itoa(clusterid), + }, + } +} + +func permission(id int, clusterid int) database.Permission { + return database.Permission{ + Database: "database" + strconv.Itoa(clusterid), + User: "user" + strconv.Itoa(id), + Cluster: database.Cluster{ + Name: "cluster" + strconv.Itoa(clusterid), + Namespace: "cluster-namespace" + strconv.Itoa(clusterid), + }, + } +} + +func TestGetDemand(t *testing.T) { + count := 0 + generatePassword = func(int, bool, bool) string { + count++ + return "password" + strconv.Itoa(count) + } + + type existing struct { + clients []clients.Resource + statefulSets []stateful_sets.Resource + secrets []secrets.Resource + databases []database.Database + users []database.User + permissions []database.Permission + } + + type expected struct { + dbToAdd []state.DemandTarget[clients.Resource, database.Database] + userToAdd []state.DemandTarget[clients.Resource, database.User] + permissionToAdd []state.DemandTarget[clients.Resource, database.Permission] + secretToAdd []state.DemandTarget[clients.Resource, secrets.Resource] + dbToRemove []database.Database + userToRemove []database.User + permissionToRemove []database.Permission + secretToRemove []secrets.Resource + } + + user1 := 1 + user2 := 2 + cluster1 := 1 + + tests := []struct { + name string + existing existing + expected expected + }{ + { + name: "no clients", + }, + { + name: "one client with offline statefulset", + existing: existing{ + clients: []clients.Resource{client(user1, cluster1, true)}, + statefulSets: []stateful_sets.Resource{statefulset(cluster1, false)}, + }, + }, + { + name: "one client", + existing: existing{ + clients: []clients.Resource{client(user1, cluster1, true)}, + statefulSets: []stateful_sets.Resource{statefulset(cluster1, true)}, + }, + expected: expected{ + dbToAdd: []state.DemandTarget[clients.Resource, database.Database]{ + state.NewDemandTarget(client(user1, cluster1, true), db(user1, cluster1)), + }, + userToAdd: []state.DemandTarget[clients.Resource, database.User]{ + state.NewDemandTarget(client(user1, cluster1, true), user(user1, cluster1, "password1")), + }, + secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + state.NewDemandTarget(client(user1, cluster1, true), secret(user1, cluster1, "password1")), + }, + }, + }, + { + name: "two clients", + existing: existing{ + clients: []clients.Resource{client(user1, cluster1, true), client(user2, cluster1, false)}, + statefulSets: []stateful_sets.Resource{statefulset(cluster1, true)}, + }, + expected: expected{ + dbToAdd: []state.DemandTarget[clients.Resource, database.Database]{ + state.NewDemandTarget(client(user1, cluster1, true), db(user1, cluster1)), + }, + userToAdd: []state.DemandTarget[clients.Resource, database.User]{ + state.NewDemandTarget(client(user1, cluster1, true), user(user1, cluster1, "password1")), + state.NewDemandTarget(client(user2, cluster1, false), user(user2, cluster1, "password2")), + }, + secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + state.NewDemandTarget(client(user1, cluster1, true), secret(user1, cluster1, "password1")), + state.NewDemandTarget(client(user2, cluster1, false), secret(user2, cluster1, "password2")), + }, + permissionToAdd: []state.DemandTarget[clients.Resource, database.Permission]{ + state.NewDemandTarget(client(user2, cluster1, false), permission(user2, cluster1)), + }, + }, + }, + } + + for _, tt := range tests { + count = 0 + t.Run(tt.name, func(t *testing.T) { + s := State{ + clients: bucket.NewBucket[clients.Resource](), + statefulSets: bucket.NewBucket[stateful_sets.Resource](), + secrets: bucket.NewBucket[secrets.Resource](), + databases: bucket.NewBucket[database.Database](), + users: bucket.NewBucket[database.User](), + permissions: bucket.NewBucket[database.Permission](), + } + + for _, c := range tt.existing.clients { + s.clients.Add(c) + } + + for _, ss := range tt.existing.statefulSets { + s.statefulSets.Add(ss) + } + + for _, sec := range tt.existing.secrets { + s.secrets.Add(sec) + } + + for _, db := range tt.existing.databases { + s.databases.Add(db) + } + + for _, u := range tt.existing.users { + s.users.Add(u) + } + + for _, p := range tt.existing.permissions { + s.permissions.Add(p) + } + + dbDemand, userDemand, permissionDemand, secretDemand := s.GetDemand() + + assert.EqualValues(t, state.NewInitializedDemand(tt.expected.dbToAdd, tt.expected.dbToRemove), dbDemand, "dbDemand") + assert.EqualValues(t, state.NewInitializedDemand(tt.expected.userToAdd, tt.expected.userToRemove), userDemand, "userDemand") + assert.EqualValues(t, state.NewInitializedDemand(tt.expected.permissionToAdd, tt.expected.permissionToRemove), permissionDemand, "permissionDemand") + assert.EqualValues(t, state.NewInitializedDemand(tt.expected.secretToAdd, tt.expected.secretToRemove), secretDemand, "secretDemand") + }) + } +} diff --git a/internal/state/demand.go b/internal/state/demand.go index b01f7a7..478a222 100644 --- a/internal/state/demand.go +++ b/internal/state/demand.go @@ -5,6 +5,10 @@ import ( "github.com/benjamin-wright/db-operator/internal/state/types" ) +func NewDemandTarget[T types.Nameable, U types.Nameable](parent T, target U) DemandTarget[T, U] { + return DemandTarget[T, U]{Parent: parent, Target: target} +} + type DemandTarget[T types.Nameable, U types.Nameable] struct { Parent T Target U @@ -30,6 +34,20 @@ func NewDemand[T types.Nameable, U types.Nameable]() Demand[T, U] { } } +func NewInitializedDemand[T types.Nameable, U types.Nameable](toAdd []DemandTarget[T, U], toRemove []U) Demand[T, U] { + d := NewDemand[T, U]() + + for _, obj := range toAdd { + d.ToAdd.Add(obj) + } + + for _, obj := range toRemove { + d.ToRemove.Add(obj) + } + + return d +} + func GetOneForOne[ T types.Nameable, U types.Nameable, From 25676c841d11fdf14b4ce2bf807cdc8c80ea2896 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Tue, 21 May 2024 20:08:23 +0100 Subject: [PATCH 17/22] working through some of the invalidation logic --- .../dbs/postgres/managers/database/state.go | 20 +-- .../postgres/managers/database/state_test.go | 139 +++++++++++++++--- 2 files changed, 120 insertions(+), 39 deletions(-) diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 0d10f24..66a478d 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -1,8 +1,6 @@ package database import ( - "fmt" - "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" @@ -144,7 +142,9 @@ func (s *State) diffDatabases(requests bucket.Bucket[state.DemandTarget[clients. return demand } -var generatePassword = utils.GeneratePassword +var generatePassword = func(user string) string { + return utils.GeneratePassword(32, true, true) +} func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.User]]) state.Demand[clients.Resource, database.User] { demand := state.NewDemand[clients.Resource, database.User]() @@ -159,7 +159,7 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso } if !userExists { - userRequest.Target.Password = generatePassword(32, true, true) + userRequest.Target.Password = generatePassword(userRequest.Target.Name) demand.ToAdd.Add(userRequest) } } @@ -173,18 +173,6 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso return demand } -func (s *State) getOwner(database string, newDatabases bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]]) (string, error) { - if db, ok := newDatabases.Get(database, ""); ok { - return db.Target.Owner, nil - } - - if db, ok := s.databases.Get(database, ""); ok { - return db.Owner, nil - } - - return "", fmt.Errorf("database %s not found", database) -} - func (s *State) diffPermissions( requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], deadUsers bucket.Bucket[database.User], diff --git a/internal/dbs/postgres/managers/database/state_test.go b/internal/dbs/postgres/managers/database/state_test.go index 245638e..8b1a96a 100644 --- a/internal/dbs/postgres/managers/database/state_test.go +++ b/internal/dbs/postgres/managers/database/state_test.go @@ -57,6 +57,12 @@ func secret(id int, clusterid int, password string) secrets.Resource { } } +func secretWithUser(id int, clusterid int, password string, user string) secrets.Resource { + s := secret(id, clusterid, password) + s.User = user + return s +} + func db(ownerid int, clusterid int) database.Database { return database.Database{ Name: "database" + strconv.Itoa(clusterid), @@ -79,6 +85,11 @@ func user(id int, clusterid int, password string) database.User { } } +func withName(u database.User, name string) database.User { + u.Name = name + return u +} + func permission(id int, clusterid int) database.Permission { return database.Permission{ Database: "database" + strconv.Itoa(clusterid), @@ -91,10 +102,8 @@ func permission(id int, clusterid int) database.Permission { } func TestGetDemand(t *testing.T) { - count := 0 - generatePassword = func(int, bool, bool) string { - count++ - return "password" + strconv.Itoa(count) + generatePassword = func(name string) string { + return name + "-pwd" } type existing struct { @@ -117,9 +126,10 @@ func TestGetDemand(t *testing.T) { secretToRemove []secrets.Resource } - user1 := 1 - user2 := 2 - cluster1 := 1 + userid1 := 1 + userid2 := 2 + // userid3 := 3 + clusterid1 := 1 tests := []struct { name string @@ -132,55 +142,138 @@ func TestGetDemand(t *testing.T) { { name: "one client with offline statefulset", existing: existing{ - clients: []clients.Resource{client(user1, cluster1, true)}, - statefulSets: []stateful_sets.Resource{statefulset(cluster1, false)}, + clients: []clients.Resource{client(userid1, clusterid1, true)}, + statefulSets: []stateful_sets.Resource{statefulset(clusterid1, false)}, }, }, { name: "one client", existing: existing{ - clients: []clients.Resource{client(user1, cluster1, true)}, - statefulSets: []stateful_sets.Resource{statefulset(cluster1, true)}, + clients: []clients.Resource{client(userid1, clusterid1, true)}, + statefulSets: []stateful_sets.Resource{statefulset(clusterid1, true)}, }, expected: expected{ dbToAdd: []state.DemandTarget[clients.Resource, database.Database]{ - state.NewDemandTarget(client(user1, cluster1, true), db(user1, cluster1)), + state.NewDemandTarget(client(userid1, clusterid1, true), db(userid1, clusterid1)), }, userToAdd: []state.DemandTarget[clients.Resource, database.User]{ - state.NewDemandTarget(client(user1, cluster1, true), user(user1, cluster1, "password1")), + state.NewDemandTarget(client(userid1, clusterid1, true), user(userid1, clusterid1, "user1-pwd")), }, secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ - state.NewDemandTarget(client(user1, cluster1, true), secret(user1, cluster1, "password1")), + state.NewDemandTarget(client(userid1, clusterid1, true), secret(userid1, clusterid1, "user1-pwd")), }, }, }, { name: "two clients", existing: existing{ - clients: []clients.Resource{client(user1, cluster1, true), client(user2, cluster1, false)}, - statefulSets: []stateful_sets.Resource{statefulset(cluster1, true)}, + clients: []clients.Resource{client(userid1, clusterid1, true), client(userid2, clusterid1, false)}, + statefulSets: []stateful_sets.Resource{statefulset(clusterid1, true)}, }, expected: expected{ dbToAdd: []state.DemandTarget[clients.Resource, database.Database]{ - state.NewDemandTarget(client(user1, cluster1, true), db(user1, cluster1)), + state.NewDemandTarget(client(userid1, clusterid1, true), db(userid1, clusterid1)), + }, + userToAdd: []state.DemandTarget[clients.Resource, database.User]{ + state.NewDemandTarget(client(userid1, clusterid1, true), user(userid1, clusterid1, "user1-pwd")), + state.NewDemandTarget(client(userid2, clusterid1, false), user(userid2, clusterid1, "user2-pwd")), + }, + secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + state.NewDemandTarget(client(userid1, clusterid1, true), secret(userid1, clusterid1, "user1-pwd")), + state.NewDemandTarget(client(userid2, clusterid1, false), secret(userid2, clusterid1, "user2-pwd")), + }, + permissionToAdd: []state.DemandTarget[clients.Resource, database.Permission]{ + state.NewDemandTarget(client(userid2, clusterid1, false), permission(userid2, clusterid1)), + }, + }, + }, + { + name: "everything exists", + existing: existing{ + clients: []clients.Resource{client(userid1, clusterid1, true), client(userid2, clusterid1, false)}, + statefulSets: []stateful_sets.Resource{statefulset(clusterid1, true)}, + databases: []database.Database{db(userid1, clusterid1)}, + users: []database.User{user(userid1, clusterid1, ""), user(userid2, clusterid1, "")}, + permissions: []database.Permission{permission(userid2, clusterid1)}, + secrets: []secrets.Resource{secret(userid1, clusterid1, "oldpwd1"), secret(userid2, clusterid1, "oldpwd2")}, + }, + }, + { + name: "missing secret", + existing: existing{ + clients: []clients.Resource{client(userid1, clusterid1, true), client(userid2, clusterid1, false)}, + statefulSets: []stateful_sets.Resource{statefulset(clusterid1, true)}, + databases: []database.Database{db(userid1, clusterid1)}, + users: []database.User{user(userid1, clusterid1, ""), user(userid2, clusterid1, "")}, + permissions: []database.Permission{permission(userid2, clusterid1)}, + secrets: []secrets.Resource{secret(userid1, clusterid1, "oldpwd1")}, + }, + expected: expected{ + userToRemove: []database.User{user(userid2, clusterid1, "")}, + userToAdd: []state.DemandTarget[clients.Resource, database.User]{ + state.NewDemandTarget(client(userid2, clusterid1, false), user(userid2, clusterid1, "user2-pwd")), + }, + permissionToRemove: []database.Permission{permission(userid2, clusterid1)}, + permissionToAdd: []state.DemandTarget[clients.Resource, database.Permission]{ + state.NewDemandTarget(client(userid2, clusterid1, false), permission(userid2, clusterid1)), }, + secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + state.NewDemandTarget(client(userid2, clusterid1, false), secret(userid2, clusterid1, "user2-pwd")), + }, + }, + }, + { + name: "missing user", + existing: existing{ + clients: []clients.Resource{client(userid1, clusterid1, true), client(userid2, clusterid1, false)}, + statefulSets: []stateful_sets.Resource{statefulset(clusterid1, true)}, + databases: []database.Database{db(userid1, clusterid1)}, + users: []database.User{user(userid1, clusterid1, "")}, + permissions: []database.Permission{permission(userid2, clusterid1)}, + secrets: []secrets.Resource{secret(userid1, clusterid1, "oldpwd1"), secret(userid2, clusterid1, "oldpwd2")}, + }, + expected: expected{ userToAdd: []state.DemandTarget[clients.Resource, database.User]{ - state.NewDemandTarget(client(user1, cluster1, true), user(user1, cluster1, "password1")), - state.NewDemandTarget(client(user2, cluster1, false), user(user2, cluster1, "password2")), + state.NewDemandTarget(client(userid2, clusterid1, false), user(userid2, clusterid1, "user2-pwd")), }, + secretToRemove: []secrets.Resource{secret(userid2, clusterid1, "oldpwd2")}, secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ - state.NewDemandTarget(client(user1, cluster1, true), secret(user1, cluster1, "password1")), - state.NewDemandTarget(client(user2, cluster1, false), secret(user2, cluster1, "password2")), + state.NewDemandTarget(client(userid2, clusterid1, false), secret(userid2, clusterid1, "user2-pwd")), }, + permissionToRemove: []database.Permission{permission(userid2, clusterid1)}, permissionToAdd: []state.DemandTarget[clients.Resource, database.Permission]{ - state.NewDemandTarget(client(user2, cluster1, false), permission(user2, cluster1)), + state.NewDemandTarget(client(userid2, clusterid1, false), permission(userid2, clusterid1)), }, }, }, + // { + // name: "wrong user name", + // existing: existing{ + // clients: []clients.Resource{client(userid1, clusterid1, true), client(userid2, clusterid1, false)}, + // statefulSets: []stateful_sets.Resource{statefulset(clusterid1, true)}, + // databases: []database.Database{db(userid1, clusterid1)}, + // users: []database.User{user(userid1, clusterid1, ""), withName(user(userid2, clusterid1, ""), "user3")}, + // permissions: []database.Permission{permission(userid3, clusterid1)}, + // secrets: []secrets.Resource{secret(userid1, clusterid1, "oldpwd1"), secretWithUser(userid2, clusterid1, "oldpwd2", "user3")}, + // }, + // expected: expected{ + // userToRemove: []database.User{withName(user(userid2, clusterid1, ""), "user3")}, + // userToAdd: []state.DemandTarget[clients.Resource, database.User]{ + // state.NewDemandTarget(client(userid2, clusterid1, false), user(userid2, clusterid1, "user2-pwd")), + // }, + // permissionToRemove: []database.Permission{permission(userid3, clusterid1)}, + // permissionToAdd: []state.DemandTarget[clients.Resource, database.Permission]{ + // state.NewDemandTarget(client(userid2, clusterid1, false), permission(userid2, clusterid1)), + // }, + // secretToRemove: []secrets.Resource{secretWithUser(userid2, clusterid1, "oldpwd2", "user3")}, + // secretToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ + // state.NewDemandTarget(client(userid2, clusterid1, false), secret(userid2, clusterid1, "user2-pwd")), + // }, + // }, + // }, } for _, tt := range tests { - count = 0 t.Run(tt.name, func(t *testing.T) { s := State{ clients: bucket.NewBucket[clients.Resource](), From 5c5122349bd2134f86184852df37e0babab12e5e Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Sun, 26 May 2024 13:15:15 +0100 Subject: [PATCH 18/22] another go at sharing passwords between user and secret --- internal/dbs/postgres/database/types.go | 2 +- .../postgres/managers/database/passwords.go | 123 ---------- .../managers/database/passwords_test.go | 227 ------------------ .../dbs/postgres/managers/database/state.go | 94 ++++---- 4 files changed, 45 insertions(+), 401 deletions(-) delete mode 100644 internal/dbs/postgres/managers/database/passwords.go delete mode 100644 internal/dbs/postgres/managers/database/passwords_test.go diff --git a/internal/dbs/postgres/database/types.go b/internal/dbs/postgres/database/types.go index 093d635..af7f6b1 100644 --- a/internal/dbs/postgres/database/types.go +++ b/internal/dbs/postgres/database/types.go @@ -42,7 +42,7 @@ type Permission struct { } func (u Permission) GetName() string { - return u.Database + u.User + return u.Database + ":" + u.User } func (u Permission) GetNamespace() string { diff --git a/internal/dbs/postgres/managers/database/passwords.go b/internal/dbs/postgres/managers/database/passwords.go deleted file mode 100644 index 2fead00..0000000 --- a/internal/dbs/postgres/managers/database/passwords.go +++ /dev/null @@ -1,123 +0,0 @@ -package database - -// import ( -// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" -// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" -// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" -// "github.com/benjamin-wright/db-operator/internal/state" -// "github.com/benjamin-wright/db-operator/internal/state/bucket" -// "github.com/benjamin-wright/db-operator/internal/utils" -// ) - -// var generatePassword = utils.GeneratePassword - -// func isUserSecret(user database.User, secret secrets.Resource) bool { -// return user.Cluster.Name == secret.Cluster.Name && user.Cluster.Namespace == secret.Cluster.Namespace && user.Name == secret.User -// } - -// func alignSecretDemand( -// secretsDemand *state.Demand[clients.Resource, secrets.Resource], -// userDemand *state.Demand[clients.Resource, database.User], -// usersBucket bucket.Bucket[database.User], -// ) { -// for _, secret := range secretsDemand.ToAdd.List() { -// userId := -1 - -// // check if the user already exists in the user demand -// for id, user := range userDemand.ToAdd.List() { -// if isUserSecret(user.Target, secret.Target) { -// userId = id -// break -// } -// } - -// // if the user exists in the user demand then everything is good -// if userId >= 0 { -// continue -// } - -// // delete if already exists -// if existing, ok := usersBucket.Get(secret.Target.User, secret.Target.Cluster.TargetName()); ok { -// userDemand.ToRemove.Add(existing) -// } - -// // re-add the user to the user demand -// userDemand.ToAdd.Add(state.DemandTarget[clients.Resource, database.User]{ -// Parent: secret.Parent, -// Target: database.User{ -// Name: secret.Target.User, -// Cluster: database.Cluster{ -// Name: secret.Target.Cluster.Name, -// Namespace: secret.Target.Cluster.Namespace, -// }, -// }, -// }) -// } -// } - -// func alignUserDemand( -// secretsDemand *state.Demand[clients.Resource, secrets.Resource], -// userDemand *state.Demand[clients.Resource, database.User], -// secretsBucket bucket.Bucket[secrets.Resource], -// ) { -// for _, user := range userDemand.ToAdd.List() { -// secretId := -1 - -// // check if the secret already exists in the secret demand -// for id, secret := range secretsDemand.ToAdd.List() { -// if isUserSecret(user.Target, secret.Target) { -// secretId = id -// break -// } -// } - -// // if the secret exists in the secret demand then everything is good -// if secretId >= 0 { -// continue -// } - -// // delete if already exists -// if existing, ok := secretsBucket.Get(user.Parent.Secret, user.Parent.Namespace); ok { -// secretsDemand.ToRemove.Add(existing) -// } - -// // re-add the secret to the secret demand -// secretsDemand.ToAdd.Add(state.DemandTarget[clients.Resource, secrets.Resource]{ -// Parent: user.Parent, -// Target: secrets.Resource{ -// Comparable: secrets.Comparable{ -// Name: user.Parent.Secret, -// Namespace: user.Parent.Namespace, -// Database: user.Parent.Database, -// User: user.Target.Name, -// Cluster: secrets.Cluster{ -// Name: user.Target.Cluster.Name, -// Namespace: user.Target.Cluster.Namespace, -// }, -// }, -// }, -// }) -// } -// } - -// func setPasswords( -// secretsDemand *state.Demand[clients.Resource, secrets.Resource], -// userDemand *state.Demand[clients.Resource, database.User], -// usersBucket bucket.Bucket[database.User], -// secretsBucket bucket.Bucket[secrets.Resource], -// ) { -// alignSecretDemand(secretsDemand, userDemand, usersBucket) -// alignUserDemand(secretsDemand, userDemand, secretsBucket) - -// for secretId, secret := range secretsDemand.ToAdd.List() { -// for userId, user := range userDemand.ToAdd.List() { -// if isUserSecret(user.Target, secret.Target) { -// password := generatePassword(32, true, true) -// userDemand.ToAdd.List()[userId].Target.Password = password -// secretsDemand.ToAdd.List()[secretId].Target.Password = password -// break -// } -// } - -// } -// } diff --git a/internal/dbs/postgres/managers/database/passwords_test.go b/internal/dbs/postgres/managers/database/passwords_test.go deleted file mode 100644 index 6b9b61f..0000000 --- a/internal/dbs/postgres/managers/database/passwords_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package database - -// import ( -// "fmt" -// "testing" - -// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/database" -// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clients" -// "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" -// "github.com/benjamin-wright/db-operator/internal/state" -// "github.com/benjamin-wright/db-operator/internal/state/bucket" -// "github.com/stretchr/testify/assert" -// ) - -// func secret(id int) secrets.Resource { -// return secrets.Resource{ -// Comparable: secrets.Comparable{ -// Name: fmt.Sprintf("secret%d", id), -// Namespace: fmt.Sprintf("namespace%d", id), -// User: fmt.Sprintf("user%d", id), -// Database: fmt.Sprintf("database%d", id), -// Cluster: secrets.Cluster{ -// Name: fmt.Sprintf("cluster%d", id), -// Namespace: fmt.Sprintf("cluster-namespace%d", id), -// }, -// }, -// } -// } - -// func user(id int) database.User { -// return database.User{ -// Name: fmt.Sprintf("user%d", id), -// Cluster: database.Cluster{ -// Name: fmt.Sprintf("cluster%d", id), -// Namespace: fmt.Sprintf("cluster-namespace%d", id), -// }, -// } -// } - -// func client(id int) clients.Resource { -// return clients.Resource{ -// Comparable: clients.Comparable{ -// Username: fmt.Sprintf("user%d", id), -// Secret: fmt.Sprintf("secret%d", id), -// Namespace: fmt.Sprintf("namespace%d", id), -// Database: fmt.Sprintf("database%d", id), -// Cluster: clients.Cluster{ -// Name: fmt.Sprintf("cluster%d", id), -// Namespace: fmt.Sprintf("cluster-namespace%d", id), -// }, -// }, -// } -// } - -// func secretDemand(id int) state.DemandTarget[clients.Resource, secrets.Resource] { -// return state.DemandTarget[clients.Resource, secrets.Resource]{ -// Parent: client(id), -// Target: secret(id), -// } -// } - -// func secretWithPassword(id int, password string) state.DemandTarget[clients.Resource, secrets.Resource] { -// secret := secretDemand(id) -// secret.Target.Password = password -// return secret -// } - -// func userDemand(id int) state.DemandTarget[clients.Resource, database.User] { -// return state.DemandTarget[clients.Resource, database.User]{ -// Parent: client(id), -// Target: user(id), -// } -// } - -// func userWithPassword(id int, password string) state.DemandTarget[clients.Resource, database.User] { -// user := userDemand(id) -// user.Target.Password = password -// return user -// } - -// func TestSetPasswords(t *testing.T) { -// generatePassword = func(int, bool, bool) string { -// return "password" -// } - -// type testDemand struct { -// secretsToAdd bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]] -// secretsToRemove []secrets.Resource -// usersToAdd []state.DemandTarget[clients.Resource, database.User] -// usersToRemove []database.User -// } - -// tests := []struct { -// name string -// input testDemand -// existingUsers []database.User -// existingSecrets []secrets.Resource -// expected testDemand -// }{ -// { -// name: "set passwords", -// input: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretDemand(1), -// }, -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userDemand(1), -// }, -// }, -// expected: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretWithPassword(1, "password"), -// }, -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userWithPassword(1, "password"), -// }, -// }, -// }, -// { -// name: "secret required but user missing from demand", -// input: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretDemand(1), -// }, -// }, -// existingUsers: []database.User{ -// user(1), -// }, -// expected: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretWithPassword(1, "password"), -// }, -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userWithPassword(1, "password"), -// }, -// usersToRemove: []database.User{ -// user(1), -// }, -// }, -// }, -// { -// name: "secret required but user missing from demand and doesn't already exist", -// input: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretDemand(1), -// }, -// }, -// expected: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretWithPassword(1, "password"), -// }, -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userWithPassword(1, "password"), -// }, -// }, -// }, -// { -// name: "user required but secret missing from demand", -// input: testDemand{ -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userDemand(1), -// }, -// }, -// existingSecrets: []secrets.Resource{ -// secret(1), -// }, -// expected: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretWithPassword(1, "password"), -// }, -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userWithPassword(1, "password"), -// }, -// secretsToRemove: []secrets.Resource{ -// secret(1), -// }, -// }, -// }, -// { -// name: "user required but secret missing from demand and doesn't already exist", -// input: testDemand{ -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userDemand(1), -// }, -// }, -// expected: testDemand{ -// secretsToAdd: []state.DemandTarget[clients.Resource, secrets.Resource]{ -// secretWithPassword(1, "password"), -// }, -// usersToAdd: []state.DemandTarget[clients.Resource, database.User]{ -// userWithPassword(1, "password"), -// }, -// }, -// }, -// } - -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// secretsDemand := state.Demand[clients.Resource, secrets.Resource]{ -// ToAdd: test.input.secretsToAdd, -// ToRemove: test.input.secretsToRemove, -// } - -// usersDemand := state.Demand[clients.Resource, database.User]{ -// ToAdd: test.input.usersToAdd, -// ToRemove: test.input.usersToRemove, -// } - -// existingUsers := bucket.NewBucket[database.User]() -// for _, user := range test.existingUsers { -// existingUsers.Add(user) -// } - -// existingSecrets := bucket.NewBucket[secrets.Resource]() -// for _, secret := range test.existingSecrets { -// existingSecrets.Add(secret) -// } - -// setPasswords(&secretsDemand, &usersDemand, existingUsers, existingSecrets) - -// assert.Equal(t, test.expected.secretsToAdd, secretsDemand.ToAdd, "secretsToAdd") -// assert.Equal(t, test.expected.secretsToRemove, secretsDemand.ToRemove, "secretsToRemove") -// assert.Equal(t, test.expected.usersToAdd, usersDemand.ToAdd, "usersToAdd") -// assert.Equal(t, test.expected.usersToRemove, usersDemand.ToRemove, "usersToRemove") -// }) -// } -// } diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 66a478d..797422a 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -19,6 +19,7 @@ type State struct { databases bucket.Bucket[database.Database] users bucket.Bucket[database.User] permissions bucket.Bucket[database.Permission] + passwords map[string]string } func (s *State) Apply(update interface{}) { @@ -149,18 +150,19 @@ var generatePassword = func(user string) string { func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.User]]) state.Demand[clients.Resource, database.User] { demand := state.NewDemand[clients.Resource, database.User]() - for _, userRequest := range requests.List() { - _, userExists := s.users.Get(userRequest.Target.GetName(), userRequest.Target.GetNamespace()) - _, secretExists := s.secrets.Get(userRequest.Parent.Secret, userRequest.Parent.Namespace) + for _, request := range requests.List() { + _, userExists := s.users.Get(request.Target.GetName(), request.Target.GetNamespace()) + _, secretExists := s.secrets.Get(request.Parent.Secret, request.Parent.Namespace) if userExists && !secretExists { - demand.ToRemove.Add(userRequest.Target) + demand.ToRemove.Add(request.Target) userExists = false } if !userExists { - userRequest.Target.Password = generatePassword(userRequest.Target.Name) - demand.ToAdd.Add(userRequest) + s.passwords[request.Parent.Name+":"+request.Parent.Namespace] = generatePassword(request.Target.Name) + request.Target.Password = s.passwords[request.Parent.Name+":"+request.Parent.Namespace] + demand.ToAdd.Add(request) } } @@ -173,68 +175,59 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso return demand } -func (s *State) diffPermissions( - requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], - deadUsers bucket.Bucket[database.User], -) state.Demand[clients.Resource, database.Permission] { - demand := state.NewDemand[clients.Resource, database.Permission]() +func (s *State) diffSecrets( + requests bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]], +) state.Demand[clients.Resource, secrets.Resource] { + demand := state.NewDemand[clients.Resource, secrets.Resource]() - for _, permissionRequest := range requests.List() { - _, permissionExists := s.permissions.Get(permissionRequest.Target.GetName(), permissionRequest.Target.GetNamespace()) - _, isRefreshing := deadUsers.Get(permissionRequest.Parent.Username, permissionRequest.Target.GetNamespace()) + for _, request := range requests.List() { + oldSecret, secretExists := s.secrets.Get(request.Target.GetName(), request.Target.GetNamespace()) + _, userExists := s.users.Get(request.Parent.Username, request.Target.Cluster.GetNamespace()) - if permissionExists { - if isRefreshing { - demand.ToRemove.Add(permissionRequest.Target) - permissionExists = false - } + if secretExists && !userExists { + demand.ToRemove.Add(oldSecret) + secretExists = false } - if !permissionExists { - demand.ToAdd.Add(permissionRequest) - continue + if !secretExists { + request.Target.Password = s.passwords[request.Parent.Name+":"+request.Parent.Namespace] + demand.ToAdd.Add(request) } } - for _, permission := range s.permissions.List() { - if _, ok := requests.Get(permission.GetName(), permission.GetNamespace()); !ok { - demand.ToRemove.Add(permission) + for _, secret := range s.secrets.List() { + if _, ok := requests.Get(secret.GetName(), secret.GetNamespace()); !ok { + demand.ToRemove.Add(secret) } } return demand } -func (s *State) diffSecrets( - requests bucket.Bucket[state.DemandTarget[clients.Resource, secrets.Resource]], - users state.Demand[clients.Resource, database.User], -) state.Demand[clients.Resource, secrets.Resource] { - demand := state.NewDemand[clients.Resource, secrets.Resource]() +func (s *State) diffPermissions( + requests bucket.Bucket[state.DemandTarget[clients.Resource, database.Permission]], +) state.Demand[clients.Resource, database.Permission] { + demand := state.NewDemand[clients.Resource, database.Permission]() - for _, secretRequest := range requests.List() { - _, secretExists := s.secrets.Get(secretRequest.Target.GetName(), secretRequest.Target.GetNamespace()) - _, isRefreshing := users.ToRemove.Get(secretRequest.Parent.Username, secretRequest.Target.Cluster.GetNamespace()) + for _, request := range requests.List() { + _, permissionExists := s.permissions.Get(request.Target.GetName(), request.Target.GetNamespace()) + _, userExists := s.users.Get(request.Parent.Username, request.Target.GetNamespace()) + _, secretExists := s.secrets.Get(request.Parent.Secret, request.Parent.Namespace) - if secretExists && isRefreshing { - demand.ToRemove.Add(secretRequest.Target) - secretExists = false + if permissionExists && (!userExists || !secretExists) { + demand.ToRemove.Add(request.Target) + permissionExists = false } - if !secretExists { - user, ok := users.ToAdd.Get(secretRequest.Parent.Username, secretRequest.Target.Cluster.GetNamespace()) - if !ok { - log.Logger.Error().Str("secret", secretRequest.Target.GetName()).Msg("wat dis? User not found for secret") - continue - } - - secretRequest.Target.Password = user.Target.Password - demand.ToAdd.Add(secretRequest) + if !permissionExists { + demand.ToAdd.Add(request) + continue } } - for _, secret := range s.secrets.List() { - if _, ok := requests.Get(secret.GetName(), secret.GetNamespace()); !ok { - demand.ToRemove.Add(secret) + for _, permission := range s.permissions.List() { + if _, ok := requests.Get(permission.GetName(), permission.GetNamespace()); !ok { + demand.ToRemove.Add(permission) } } @@ -249,10 +242,11 @@ func (s *State) GetDemand() ( ) { dbRequests, userRequests, permissionRequests, secretRequests := s.getRequests() + s.passwords = map[string]string{} dbDemand := s.diffDatabases(dbRequests) userDemand := s.diffUsers(userRequests) - permissionDemand := s.diffPermissions(permissionRequests, userDemand.ToRemove) - secretsDemand := s.diffSecrets(secretRequests, userDemand) + permissionDemand := s.diffPermissions(permissionRequests) + secretsDemand := s.diffSecrets(secretRequests) return dbDemand, userDemand, permissionDemand, secretsDemand } From 0a039f63475a1dfae35fa696ca956e2b1abe1daa Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 27 May 2024 14:58:02 +0100 Subject: [PATCH 19/22] connections working --- .envrc | 2 +- go.mod | 7 +- go.sum | 183 +----------------- internal/dbs/postgres/database/client.go | 10 +- internal/dbs/postgres/database/migrations.go | 109 ----------- .../k8s/stateful_sets/stateful_sets.go | 4 + .../dbs/postgres/managers/database/state.go | 89 +++++---- internal/test_utils/postgres/test_utils.go | 10 +- justfile | 3 + pkg/k8s_generic/builder.go | 4 + pkg/k8s_generic/client.go | 2 +- pkg/postgres/{ => admin}/admin.go | 73 +++---- pkg/postgres/{ => config}/config.go | 25 ++- pkg/postgres/config/connect.go | 65 +++++++ pkg/postgres/connect.go | 59 ------ pkg/postgres/migrations/client.go | 152 +++++++++++++++ pkg/postgres/migrations/load.go | 50 +++++ pkg/postgres/migrations/types.go | 11 ++ pkg/test/postgres/postgres.go | 6 +- tests/postgres_test.go | 64 ++++-- tests/tests.go | 10 + 21 files changed, 469 insertions(+), 469 deletions(-) delete mode 100644 internal/dbs/postgres/database/migrations.go rename pkg/postgres/{ => admin}/admin.go (66%) rename pkg/postgres/{ => config}/config.go (85%) create mode 100644 pkg/postgres/config/connect.go delete mode 100644 pkg/postgres/connect.go create mode 100644 pkg/postgres/migrations/client.go create mode 100644 pkg/postgres/migrations/load.go create mode 100644 pkg/postgres/migrations/types.go diff --git a/.envrc b/.envrc index 58d9e58..7a04b7a 100644 --- a/.envrc +++ b/.envrc @@ -1,3 +1,3 @@ #!/bin/bash -export KUBECONFIG=.scratch/kubeconfig \ No newline at end of file +export KUBECONFIG=$(pwd)/.scratch/kubeconfig \ No newline at end of file diff --git a/go.mod b/go.mod index 9a730c1..42174b0 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,7 @@ toolchain go1.22.1 require ( github.com/go-redis/redis/v8 v8.11.5 - github.com/jackc/pgconn v1.14.3 - github.com/jackc/pgx/v4 v4.18.3 + github.com/jackc/pgx/v5 v5.6.0 github.com/rs/zerolog v1.32.0 github.com/stretchr/testify v1.9.0 k8s.io/api v0.30.1 @@ -31,12 +30,8 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/pgtype v1.14.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect diff --git a/go.sum b/go.sum index 8d525b8..69327d8 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,6 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -19,8 +12,6 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -31,13 +22,10 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -52,92 +40,32 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= -github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -157,90 +85,35 @@ github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= @@ -248,69 +121,30 @@ golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -318,23 +152,18 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= diff --git a/internal/dbs/postgres/database/client.go b/internal/dbs/postgres/database/client.go index 7cc0d77..0728f5d 100644 --- a/internal/dbs/postgres/database/client.go +++ b/internal/dbs/postgres/database/client.go @@ -4,13 +4,14 @@ import ( "fmt" "strings" - "github.com/benjamin-wright/db-operator/pkg/postgres" + "github.com/benjamin-wright/db-operator/pkg/postgres/admin" + "github.com/benjamin-wright/db-operator/pkg/postgres/config" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) type Client struct { - conn *postgres.AdminConn + conn *admin.Client cluster string namespace string database string @@ -18,12 +19,13 @@ type Client struct { } func New(cluster string, namespace string, password string, database string) (*Client, error) { - cfg := postgres.ConnectConfig{ + cfg := config.Config{ Host: fmt.Sprintf("%s.%s.svc.cluster.local", cluster, namespace), Port: 26257, Username: "postgres", Password: password, Database: database, + Retry: false, } logger := log.With(). @@ -39,7 +41,7 @@ func New(cluster string, namespace string, password string, database string) (*C logger.Debug().Msgf("Opening connection to Postgres Cluster %s", cluster) } - conn, err := postgres.NewAdminConn(cfg) + conn, err := admin.New(cfg) if err != nil { return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", cluster, err) } diff --git a/internal/dbs/postgres/database/migrations.go b/internal/dbs/postgres/database/migrations.go deleted file mode 100644 index 4696847..0000000 --- a/internal/dbs/postgres/database/migrations.go +++ /dev/null @@ -1,109 +0,0 @@ -package database - -import ( - "context" - "fmt" - - "github.com/benjamin-wright/db-operator/pkg/postgres" - "github.com/jackc/pgx/v4" - "github.com/rs/zerolog/log" -) - -type MigrationsClient struct { - conn *pgx.Conn - deployment string - namespace string - database string -} - -func NewMigrations(deployment string, namespace string, database string) (*MigrationsClient, error) { - cfg := postgres.ConnectConfig{ - Host: fmt.Sprintf("%s.%s.svc.cluster.local", deployment, namespace), - Port: 26257, - Username: "root", - Database: database, - } - - conn, err := postgres.Connect(cfg) - if err != nil { - return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", database, err) - } - - return &MigrationsClient{ - conn: conn, - deployment: deployment, - namespace: namespace, - database: database, - }, nil -} - -func (c *MigrationsClient) Stop() { - log.Info().Msgf("Closing connection to DB %s[%s]", c.deployment, c.database) - c.conn.Close(context.TODO()) -} - -func (c *MigrationsClient) HasMigrationsTable() (bool, error) { - rows, err := c.conn.Query(context.TODO(), "SELECT DISTINCT(tablename) FROM pg_catalog.pg_tables WHERE tablename = $1", "migrations") - if err != nil { - return false, fmt.Errorf("failed to check for migrations: %+v", err) - } - defer rows.Close() - - return rows.Next(), nil -} - -func (d *MigrationsClient) CreateMigrationsTable() error { - _, err := d.conn.Exec( - context.TODO(), - ` - CREATE TABLE migrations ( - id INT PRIMARY KEY NOT NULL UNIQUE - ); - `, - ) - - return err -} - -func (c *MigrationsClient) AppliedMigrations() ([]Migration, error) { - rows, err := c.conn.Query(context.TODO(), "SELECT id FROM migrations") - if err != nil { - return nil, fmt.Errorf("failed to get migration ids: %+v", err) - } - defer rows.Close() - - migrations := []Migration{} - - for rows.Next() { - var id int64 - err = rows.Scan(&id) - if err != nil { - return nil, fmt.Errorf("failed to parse database response: %+v", err) - } - - migrations = append(migrations, Migration{ - Cluster: Cluster{ - Name: c.deployment, - Namespace: c.namespace, - }, - Database: c.database, - Index: id, - }) - } - - return migrations, nil -} - -func (c *MigrationsClient) RunMigration(index int64, query string) error { - _, err := c.conn.Exec(context.TODO(), query) - if err != nil { - return fmt.Errorf("failed to run migration: %+v", err) - } - - _, err = c.conn.Exec(context.TODO(), "INSERT INTO migrations (id) VALUES ($1)", index) - if err != nil { - return fmt.Errorf("failed to update migrations table: %+v", err) - } - - return nil -} diff --git a/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go b/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go index 09b91e0..45dee7c 100644 --- a/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go +++ b/internal/dbs/postgres/k8s/stateful_sets/stateful_sets.go @@ -75,6 +75,10 @@ func (r Resource) ToUnstructured() *unstructured.Unstructured { "name": "POSTGRES_PASSWORD", "value": "postgres", }, + { + "name": "ALLOW_EMPTY_PASSWORD", + "value": "false", + }, }, "resources": map[string]interface{}{ "requests": map[string]interface{}{ diff --git a/internal/dbs/postgres/managers/database/state.go b/internal/dbs/postgres/managers/database/state.go index 797422a..7a9ed44 100644 --- a/internal/dbs/postgres/managers/database/state.go +++ b/internal/dbs/postgres/managers/database/state.go @@ -47,6 +47,54 @@ func (s *State) ClearRemote() { s.permissions.Clear() } +func (s *State) clearPasswords() { + s.passwords = map[string]string{} +} + +func (s *State) getPassword(client clients.Resource) string { + if password, ok := s.passwords[client.Name+":"+client.Namespace]; ok { + return password + } + + password := generatePassword(client.Username) + s.passwords[client.Name+":"+client.Namespace] = password + return password +} + +func (s *State) GetActiveClusters() []database.Cluster { + clusters := []database.Cluster{} + + for _, ss := range s.statefulSets.List() { + if !ss.IsReady() { + continue + } + + clusters = append(clusters, database.Cluster{ + Name: ss.Name, + Namespace: ss.Namespace, + }) + } + + return clusters +} + +func (s *State) GetDemand() ( + state.Demand[clients.Resource, database.Database], + state.Demand[clients.Resource, database.User], + state.Demand[clients.Resource, database.Permission], + state.Demand[clients.Resource, secrets.Resource], +) { + dbRequests, userRequests, permissionRequests, secretRequests := s.getRequests() + + s.clearPasswords() + dbDemand := s.diffDatabases(dbRequests) + userDemand := s.diffUsers(userRequests) + permissionDemand := s.diffPermissions(permissionRequests) + secretsDemand := s.diffSecrets(secretRequests) + + return dbDemand, userDemand, permissionDemand, secretsDemand +} + func (s *State) getRequests() ( bucket.Bucket[state.DemandTarget[clients.Resource, database.Database]], bucket.Bucket[state.DemandTarget[clients.Resource, database.User]], @@ -144,7 +192,7 @@ func (s *State) diffDatabases(requests bucket.Bucket[state.DemandTarget[clients. } var generatePassword = func(user string) string { - return utils.GeneratePassword(32, true, true) + return utils.GeneratePassword(32, true, false) } func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Resource, database.User]]) state.Demand[clients.Resource, database.User] { @@ -160,8 +208,7 @@ func (s *State) diffUsers(requests bucket.Bucket[state.DemandTarget[clients.Reso } if !userExists { - s.passwords[request.Parent.Name+":"+request.Parent.Namespace] = generatePassword(request.Target.Name) - request.Target.Password = s.passwords[request.Parent.Name+":"+request.Parent.Namespace] + request.Target.Password = s.getPassword(request.Parent) demand.ToAdd.Add(request) } } @@ -190,7 +237,7 @@ func (s *State) diffSecrets( } if !secretExists { - request.Target.Password = s.passwords[request.Parent.Name+":"+request.Parent.Namespace] + request.Target.Password = s.getPassword(request.Parent) demand.ToAdd.Add(request) } } @@ -233,37 +280,3 @@ func (s *State) diffPermissions( return demand } - -func (s *State) GetDemand() ( - state.Demand[clients.Resource, database.Database], - state.Demand[clients.Resource, database.User], - state.Demand[clients.Resource, database.Permission], - state.Demand[clients.Resource, secrets.Resource], -) { - dbRequests, userRequests, permissionRequests, secretRequests := s.getRequests() - - s.passwords = map[string]string{} - dbDemand := s.diffDatabases(dbRequests) - userDemand := s.diffUsers(userRequests) - permissionDemand := s.diffPermissions(permissionRequests) - secretsDemand := s.diffSecrets(secretRequests) - - return dbDemand, userDemand, permissionDemand, secretsDemand -} - -func (s *State) GetActiveClusters() []database.Cluster { - clusters := []database.Cluster{} - - for _, ss := range s.statefulSets.List() { - if !ss.IsReady() { - continue - } - - clusters = append(clusters, database.Cluster{ - Name: ss.Name, - Namespace: ss.Namespace, - }) - } - - return clusters -} diff --git a/internal/test_utils/postgres/test_utils.go b/internal/test_utils/postgres/test_utils.go index 356234c..7a11393 100644 --- a/internal/test_utils/postgres/test_utils.go +++ b/internal/test_utils/postgres/test_utils.go @@ -4,16 +4,16 @@ import ( "context" "testing" - "github.com/benjamin-wright/db-operator/pkg/postgres" - "github.com/jackc/pgx/v4" + "github.com/benjamin-wright/db-operator/pkg/postgres/config" + "github.com/jackc/pgx/v5" ) type TestUtils struct { conn *pgx.Conn } -func New(cfg postgres.ConnectConfig) (*TestUtils, error) { - conn, err := postgres.Connect(cfg) +func New(cfg config.Config) (*TestUtils, error) { + conn, err := config.Connect(cfg) if err != nil { return nil, err } @@ -30,7 +30,7 @@ func (u *TestUtils) GetTableNames(t *testing.T) []string { } defer rows.Close() - var tableNames []string + tableNames := []string{} for rows.Next() { var tableName string if err := rows.Scan(&tableName); err != nil { diff --git a/justfile b/justfile index caedf7c..44d9aa8 100644 --- a/justfile +++ b/justfile @@ -44,6 +44,9 @@ tilt: test: go test --short -v ./... +int-test: + NAMESPACE=test-ns go test -v ./tests/... + build IMAGE_TAG: mkdir -p ./dist CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ./dist/app ./cmd/operator/main.go diff --git a/pkg/k8s_generic/builder.go b/pkg/k8s_generic/builder.go index d3dfa95..f64c9a6 100644 --- a/pkg/k8s_generic/builder.go +++ b/pkg/k8s_generic/builder.go @@ -1,6 +1,7 @@ package k8s_generic import ( + "fmt" "os" "k8s.io/apimachinery/pkg/runtime/schema" @@ -23,6 +24,9 @@ func NewBuilder() (*Builder, error) { var err error if kubeconfig != "" { + if err != nil { + return nil, fmt.Errorf("failed to get current working directory: %+v", err) + } config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, err diff --git a/pkg/k8s_generic/client.go b/pkg/k8s_generic/client.go index 1be3069..1904561 100644 --- a/pkg/k8s_generic/client.go +++ b/pkg/k8s_generic/client.go @@ -124,7 +124,7 @@ func (c *Client[T]) DeleteAll(ctx context.Context, namespace string) error { c.logger.Info().Msgf("Deleting all resources in %s", namespace) err := c.client.Resource(c.schema).Namespace(namespace).DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{}) if err != nil { - return fmt.Errorf("failed to delete all resources: %+v", err) + return fmt.Errorf("failed to delete all resources in %s: %+v", namespace, err) } return nil diff --git a/pkg/postgres/admin.go b/pkg/postgres/admin/admin.go similarity index 66% rename from pkg/postgres/admin.go rename to pkg/postgres/admin/admin.go index d4b8a29..db2657b 100644 --- a/pkg/postgres/admin.go +++ b/pkg/postgres/admin/admin.go @@ -1,32 +1,33 @@ -package postgres +package admin import ( "context" "fmt" "regexp" - "github.com/jackc/pgx/v4" + "github.com/benjamin-wright/db-operator/pkg/postgres/config" + "github.com/jackc/pgx/v5" ) -type AdminConn struct { +type Client struct { conn *pgx.Conn } -func NewAdminConn(cfg ConnectConfig) (*AdminConn, error) { - conn, err := Connect(cfg) +func New(cfg config.Config) (*Client, error) { + conn, err := config.Connect(cfg) if err != nil { return nil, err } - return &AdminConn{conn}, nil + return &Client{conn}, nil } -func (d *AdminConn) Stop() { - d.conn.Close(context.Background()) +func (c *Client) Stop() { + c.conn.Close(context.Background()) } -func (d *AdminConn) ListUsers() ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SELECT usename FROM pg_catalog.pg_user") +func (c *Client) ListUsers() ([]string, error) { + rows, err := c.conn.Query(context.Background(), "SELECT usename FROM pg_catalog.pg_user") if err != nil { return nil, fmt.Errorf("failed to list users: %+v", err) } @@ -46,13 +47,13 @@ func (d *AdminConn) ListUsers() ([]string, error) { return users, nil } -func (d *AdminConn) CreateUser(username string, password string) error { +func (c *Client) CreateUser(username string, password string) error { if password != "" { - if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)+" WITH PASSWORD '"+sanitize(password)+"'"); err != nil { + if _, err := c.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)+" WITH PASSWORD '"+password+"'"); err != nil { return fmt.Errorf("failed to create database user: %+v", err) } } else { - if _, err := d.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)); err != nil { + if _, err := c.conn.Exec(context.Background(), "CREATE USER "+sanitize(username)); err != nil { return fmt.Errorf("failed to create database user: %+v", err) } } @@ -60,20 +61,20 @@ func (d *AdminConn) CreateUser(username string, password string) error { return nil } -func (d *AdminConn) DropUser(username string) error { - if _, err := d.conn.Exec(context.Background(), "DROP OWNED BY "+sanitize(username)); err != nil { +func (c *Client) DropUser(username string) error { + if _, err := c.conn.Exec(context.Background(), "DROP OWNED BY "+sanitize(username)+" CASCADE"); err != nil { return fmt.Errorf("failed to revoke user permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), "DROP USER "+sanitize(username)); err != nil { + if _, err := c.conn.Exec(context.Background(), "DROP USER "+sanitize(username)); err != nil { return fmt.Errorf("failed to drop database user: %+v", err) } return nil } -func (d *AdminConn) ListDatabases() ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SELECT datname FROM pg_catalog.pg_database") +func (c *Client) ListDatabases() ([]string, error) { + rows, err := c.conn.Query(context.Background(), "SELECT datname FROM pg_catalog.pg_database") if err != nil { return nil, fmt.Errorf("failed to list databases: %+v", err) } @@ -96,32 +97,32 @@ func sanitize(name string) string { return pgx.Identifier.Sanitize([]string{name}) } -func (d *AdminConn) CreateDatabase(database string) error { - if _, err := d.conn.Exec(context.Background(), "CREATE DATABASE "+sanitize(database)); err != nil { +func (c *Client) CreateDatabase(database string) error { + if _, err := c.conn.Exec(context.Background(), "CREATE DATABASE "+sanitize(database)); err != nil { return fmt.Errorf("failed to create database: %+v", err) } return nil } -func (d *AdminConn) DropDatabase(database string) error { - if _, err := d.conn.Exec(context.Background(), "DROP DATABASE "+sanitize(database)+" WITH (FORCE)"); err != nil { +func (c *Client) DropDatabase(database string) error { + if _, err := c.conn.Exec(context.Background(), "DROP DATABASE "+sanitize(database)+" WITH (FORCE)"); err != nil { return fmt.Errorf("failed to drop database: %+v", err) } return nil } -func (d *AdminConn) SetOwner(database string, user string) error { - if _, err := d.conn.Exec(context.Background(), "ALTER DATABASE "+sanitize(database)+" OWNER TO "+sanitize(user)); err != nil { +func (c *Client) SetOwner(database string, user string) error { + if _, err := c.conn.Exec(context.Background(), "ALTER DATABASE "+sanitize(database)+" OWNER TO "+sanitize(user)); err != nil { return fmt.Errorf("failed to set database owner: %+v", err) } return nil } -func (d *AdminConn) GetOwner(database string) (string, error) { - rows, err := d.conn.Query(context.Background(), "SELECT B.usename FROM pg_database A INNER JOIN pg_user B ON A.datdba = B.usesysid WHERE A.datname = $1", database) +func (c *Client) GetOwner(database string) (string, error) { + rows, err := c.conn.Query(context.Background(), "SELECT B.usename FROM pg_database A INNER JOIN pg_user B ON A.datdba = B.usesysid WHERE A.datname = $1", database) if err != nil { return "", fmt.Errorf("failed to get database owner: %+v", err) } @@ -141,8 +142,8 @@ func (d *AdminConn) GetOwner(database string) (string, error) { var ACL_REGEX = regexp.MustCompile(`^{(\w+)=.*\/.*}$`) -func (d *AdminConn) ListPermitted(database string) ([]string, error) { - rows, err := d.conn.Query(context.Background(), "SELECT defaclacl FROM pg_default_acl") +func (c *Client) ListPermitted(database string) ([]string, error) { + rows, err := c.conn.Query(context.Background(), "SELECT defaclacl FROM pg_default_acl") if err != nil { return nil, fmt.Errorf("failed to list permissions: %+v", err) } @@ -177,32 +178,32 @@ func (d *AdminConn) ListPermitted(database string) ([]string, error) { return permitted, nil } -func (d *AdminConn) GrantPermissions(username string, owner string) error { - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public GRANT INSERT, SELECT, UPDATE, DELETE ON TABLES TO %s", sanitize(owner), sanitize(username))); err != nil { +func (c *Client) GrantPermissions(username string, owner string) error { + if _, err := c.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public GRANT INSERT, SELECT, UPDATE, DELETE ON TABLES TO %s", sanitize(owner), sanitize(username))); err != nil { return fmt.Errorf("failed to grant default table permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public GRANT SELECT, UPDATE ON SEQUENCES TO %s", sanitize(owner), sanitize(username))); err != nil { + if _, err := c.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public GRANT SELECT, UPDATE ON SEQUENCES TO %s", sanitize(owner), sanitize(username))); err != nil { return fmt.Errorf("failed to grant default sequence permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO %s", sanitize(username))); err != nil { + if _, err := c.conn.Exec(context.Background(), fmt.Sprintf("GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO %s", sanitize(username))); err != nil { return fmt.Errorf("failed to grant existing table permissions: %+v", err) } return nil } -func (d *AdminConn) RevokePermissions(username string, owner string) error { - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public REVOKE INSERT, SELECT, UPDATE, DELETE ON TABLES FROM %s", sanitize(owner), sanitize(username))); err != nil { +func (c *Client) RevokePermissions(username string, owner string) error { + if _, err := c.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public REVOKE INSERT, SELECT, UPDATE, DELETE ON TABLES FROM %s", sanitize(owner), sanitize(username))); err != nil { return fmt.Errorf("failed to revoke default table permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public REVOKE SELECT, UPDATE ON SEQUENCES FROM %s", sanitize(owner), sanitize(username))); err != nil { + if _, err := c.conn.Exec(context.Background(), fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR USER %s IN SCHEMA public REVOKE SELECT, UPDATE ON SEQUENCES FROM %s", sanitize(owner), sanitize(username))); err != nil { return fmt.Errorf("failed to revoke default sequence permissions: %+v", err) } - if _, err := d.conn.Exec(context.Background(), fmt.Sprintf("REVOKE INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM %s", sanitize(username))); err != nil { + if _, err := c.conn.Exec(context.Background(), fmt.Sprintf("REVOKE INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM %s", sanitize(username))); err != nil { return fmt.Errorf("failed to revoke existing table permissions: %+v", err) } diff --git a/pkg/postgres/config.go b/pkg/postgres/config/config.go similarity index 85% rename from pkg/postgres/config.go rename to pkg/postgres/config/config.go index 40acd85..864b2d9 100644 --- a/pkg/postgres/config.go +++ b/pkg/postgres/config/config.go @@ -1,4 +1,4 @@ -package postgres +package config import ( "errors" @@ -6,18 +6,21 @@ import ( "net/url" "os" "strconv" + "time" ) -type ConnectConfig struct { +type Config struct { Host string Port int Username string Password string Database string + Timeout time.Duration + Retry bool } -func ConfigFromEnv() (ConnectConfig, error) { - empty := ConnectConfig{} +func FromEnv() (Config, error) { + empty := Config{} host, ok := os.LookupEnv("POSTGRES_HOST") if !ok { @@ -46,17 +49,18 @@ func ConfigFromEnv() (ConnectConfig, error) { database = "default" } - return ConnectConfig{ + return Config{ Host: host, Port: port, Username: user, Password: password, Database: database, + Retry: true, }, nil } -func AdminFromEnv() (ConnectConfig, error) { - empty := ConnectConfig{} +func AdminFromEnv() (Config, error) { + empty := Config{} host, ok := os.LookupEnv("POSTGRES_HOST") if !ok { @@ -80,15 +84,16 @@ func AdminFromEnv() (ConnectConfig, error) { password, _ := os.LookupEnv("POSTGRES_ADMIN_PASS") - return ConnectConfig{ + return Config{ Host: host, Port: port, Username: user, Password: password, + Retry: true, }, nil } -func (c ConnectConfig) ConnectionString() string { +func (c Config) ConnectionString() string { dbSuffix := "" if c.Database != "" { dbSuffix = "/" + c.Database @@ -102,7 +107,7 @@ func (c ConnectConfig) ConnectionString() string { return fmt.Sprintf("postgresql://%s%s@%s:%d%s", c.Username, password, c.Host, c.Port, dbSuffix) } -func (c ConnectConfig) String() string { +func (c Config) String() string { dbSuffix := "" if c.Database != "" { dbSuffix = "/" + c.Database diff --git a/pkg/postgres/config/connect.go b/pkg/postgres/config/connect.go new file mode 100644 index 0000000..5da1428 --- /dev/null +++ b/pkg/postgres/config/connect.go @@ -0,0 +1,65 @@ +package config + +import ( + "context" + "errors" + "time" + + "github.com/jackc/pgx/v5" + "github.com/rs/zerolog/log" +) + +func getConnection(config *pgx.ConnConfig, retry bool) (*pgx.Conn, error) { + attempts := 0 + limit := 1 + if retry { + limit = 10 + } + backoff := time.Duration(1) + var connection *pgx.Conn + var err error + for attempts < limit { + attempts += 1 + connection, err = pgx.ConnectConfig(context.Background(), config) + if err != nil { + log.Debug().Err(err).Msg("Failed to connect") + time.Sleep(time.Second * backoff) + backoff = backoff + time.Duration(1) + } else { + log.Debug().Msg("Connected") + break + } + } + + if connection == nil { + return nil, err + } + + return connection, err +} + +func Connect(config Config) (*pgx.Conn, error) { + connectionString := config.ConnectionString() + + pgxConfig, err := pgx.ParseConfig(connectionString) + if err != nil { + return nil, err + } + + if config.Timeout != 0 { + pgxConfig.ConnectTimeout = config.Timeout + } else { + pgxConfig.ConnectTimeout = time.Second * 2 + } + + conn, err := getConnection(pgxConfig, config.Retry) + if err != nil { + return nil, err + } + + if conn == nil { + return nil, errors.New("failed to create connection without error") + } + + return conn, nil +} diff --git a/pkg/postgres/connect.go b/pkg/postgres/connect.go deleted file mode 100644 index 3358c61..0000000 --- a/pkg/postgres/connect.go +++ /dev/null @@ -1,59 +0,0 @@ -package postgres - -import ( - "context" - "errors" - "time" - - "github.com/jackc/pgx/v4" - "github.com/rs/zerolog/log" -) - -func getConnection(config *pgx.ConnConfig) *pgx.Conn { - finished := make(chan *pgx.Conn, 1) - - go func(finished chan<- *pgx.Conn) { - attempts := 0 - limit := 10 - backoff := time.Duration(1) - var connection *pgx.Conn - var err error - for attempts < limit { - attempts += 1 - connection, err = pgx.ConnectConfig(context.Background(), config) - if err != nil { - time.Sleep(time.Second * backoff) - backoff = backoff + time.Duration(1) - } else { - log.Debug().Msg("Connected") - break - } - } - - if connection == nil { - log.Warn().Err(err).Msg("Failed to connect") - } - - finished <- connection - }(finished) - - return <-finished -} - -func Connect(config ConnectConfig) (*pgx.Conn, error) { - connectionString := config.ConnectionString() - - pgxConfig, err := pgx.ParseConfig(connectionString) - if err != nil { - return nil, err - } - - pgxConfig.ConnectTimeout = time.Second * 2 - - conn := getConnection(pgxConfig) - if conn == nil { - return nil, errors.New("failed to create connection, exiting") - } - - return conn, nil -} diff --git a/pkg/postgres/migrations/client.go b/pkg/postgres/migrations/client.go new file mode 100644 index 0000000..de9a4e4 --- /dev/null +++ b/pkg/postgres/migrations/client.go @@ -0,0 +1,152 @@ +package migrations + +import ( + "context" + "crypto/md5" + "fmt" + "sort" + + "github.com/benjamin-wright/db-operator/pkg/postgres/config" + "github.com/jackc/pgx/v5" + "github.com/rs/zerolog/log" +) + +type Client struct { + conn *pgx.Conn + cfg config.Config +} + +func New(cfg config.Config) (*Client, error) { + conn, err := config.Connect(cfg) + if err != nil { + return nil, fmt.Errorf("failed to connect to postgres db at %s: %+v", cfg.String(), err) + } + + return &Client{ + conn: conn, + cfg: cfg, + }, nil +} + +func (c *Client) Stop() { + log.Info().Msgf("Closing connection to DB %s[%s]", c.cfg.Host, c.cfg.Database) + c.conn.Close(context.TODO()) +} + +func (c *Client) Init() error { + exists, err := c.hasMigrationsTable() + if err != nil { + return err + } + + if exists { + log.Debug().Msg("Migrations table already exists") + return nil + } + + log.Debug().Msg("Creating migrations table") + return c.createMigrationsTable() +} + +func (c *Client) hasMigrationsTable() (bool, error) { + rows, err := c.conn.Query(context.TODO(), "SELECT DISTINCT(tablename) FROM pg_catalog.pg_tables WHERE tablename = $1", "migrations") + if err != nil { + return false, fmt.Errorf("failed to check for migrations: %+v", err) + } + defer rows.Close() + + return rows.Next(), nil +} + +func (d *Client) createMigrationsTable() error { + _, err := d.conn.Exec( + context.TODO(), + ` + CREATE TABLE migrations ( + id INT PRIMARY KEY NOT NULL UNIQUE, + hash CHAR(32) NOT NULL + ); + `, + ) + + return err +} + +func getHash(value string) string { + return fmt.Sprintf("%x", md5.Sum([]byte(value))) +} + +func (c *Client) Run(migrations []Migration) error { + sort.Slice(migrations, func(i, j int) bool { + return migrations[i].Index < migrations[j].Index + }) + + for _, migration := range migrations { + hash := getHash(migration.Query) + + applied, err := c.isApplied(migration.Index, hash) + if err != nil { + return err + } + + if applied { + log.Debug().Msgf("Migration %d already applied", migration.Index) + continue + } + + log.Debug().Msgf("Running migration %d", migration.Index) + err = c.runMigration(migration.Query) + if err != nil { + return err + } + + err = c.setApplied(migration.Index, hash) + if err != nil { + return err + } + } + + return nil +} + +func (c *Client) isApplied(index int, hash string) (bool, error) { + rows, err := c.conn.Query(context.TODO(), "SELECT hash FROM migrations WHERE id = $1", index) + if err != nil { + return false, fmt.Errorf("failed to check for migration: %+v", err) + } + defer rows.Close() + + if !rows.Next() { + return false, nil + } + + var appliedHash string + err = rows.Scan(&appliedHash) + if err != nil { + return false, fmt.Errorf("failed to parse database response: %+v", err) + } + + if appliedHash != hash { + return false, fmt.Errorf("hash mismatch for migration %d", index) + } + + return true, nil +} + +func (c *Client) runMigration(query string) error { + _, err := c.conn.Exec(context.TODO(), query) + if err != nil { + return fmt.Errorf("failed to run migration: %+v", err) + } + + return nil +} + +func (c *Client) setApplied(index int, hash string) error { + _, err := c.conn.Exec(context.TODO(), "INSERT INTO migrations (id, hash) VALUES ($1, $2)", index, hash) + if err != nil { + return fmt.Errorf("failed to update migrations table: %+v", err) + } + + return nil +} diff --git a/pkg/postgres/migrations/load.go b/pkg/postgres/migrations/load.go new file mode 100644 index 0000000..750a06d --- /dev/null +++ b/pkg/postgres/migrations/load.go @@ -0,0 +1,50 @@ +package migrations + +import ( + "fmt" + "os" + "regexp" + "strconv" +) + +func LoadMigrations(filepath string) ([]Migration, error) { + files, err := os.ReadDir(filepath) + if err != nil { + return nil, fmt.Errorf("failed to read migrations directory: %+v", err) + } + + migrations := make([]Migration, 0, len(files)) + for _, file := range files { + if file.IsDir() { + continue + } + + query, err := os.ReadFile(filepath + "/" + file.Name()) + if err != nil { + return nil, fmt.Errorf("failed to read migration file %s: %+v", file.Name(), err) + } + + index, err := getIndexFromFilenamePrefix(file.Name()) + if err != nil { + return nil, fmt.Errorf("failed to get index from filename %s: %+v", file.Name(), err) + } + + migrations = append(migrations, Migration{ + Index: index, + Query: string(query), + }) + } + + return migrations, nil +} + +var FILE_ID_PREFIX = regexp.MustCompile(`^(\d+)-`) + +func getIndexFromFilenamePrefix(filename string) (int, error) { + matches := FILE_ID_PREFIX.FindStringSubmatch(filename) + if len(matches) != 2 { + return 0, fmt.Errorf("failed to parse migration filename %s", filename) + } + + return strconv.Atoi(matches[1]) +} diff --git a/pkg/postgres/migrations/types.go b/pkg/postgres/migrations/types.go new file mode 100644 index 0000000..2c4455b --- /dev/null +++ b/pkg/postgres/migrations/types.go @@ -0,0 +1,11 @@ +package migrations + +type AppliedMigration struct { + Index int + Hash string +} + +type Migration struct { + Index int + Query string +} diff --git a/pkg/test/postgres/postgres.go b/pkg/test/postgres/postgres.go index fef0654..11f9b22 100644 --- a/pkg/test/postgres/postgres.go +++ b/pkg/test/postgres/postgres.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/benjamin-wright/db-operator/pkg/postgres" + "github.com/benjamin-wright/db-operator/pkg/postgres/config" "github.com/rs/zerolog/log" ) @@ -43,12 +43,12 @@ func Run(name string, port int64) func() { } func Migrate(path string) { - cfg, err := postgres.ConfigFromEnv() + cfg, err := config.FromEnv() if err != nil { log.Fatal().Err(err).Msg("Failed to get connection details") } - conn, err := postgres.Connect(cfg) + conn, err := config.Connect(cfg) if err != nil { log.Fatal().Err(err).Msg("Failed to connect to postgres") } diff --git a/tests/postgres_test.go b/tests/postgres_test.go index 084b9bd..84fc69d 100644 --- a/tests/postgres_test.go +++ b/tests/postgres_test.go @@ -12,7 +12,8 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/clusters" "github.com/benjamin-wright/db-operator/internal/dbs/postgres/k8s/secrets" postgres_helpers "github.com/benjamin-wright/db-operator/internal/test_utils/postgres" - "github.com/benjamin-wright/db-operator/pkg/postgres" + "github.com/benjamin-wright/db-operator/pkg/postgres/config" + "github.com/benjamin-wright/db-operator/pkg/postgres/migrations" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) @@ -28,6 +29,8 @@ func TestPostgresIntegration(t *testing.T) { zerolog.SetGlobalLevel(zerolog.Disabled) } + seed := randomString(8) + client, err := k8s.New() if !assert.NoError(t, err) { t.FailNow() @@ -50,9 +53,12 @@ func TestPostgresIntegration(t *testing.T) { return nil })) + clusterName := "test-db-" + seed + dbName := "db-" + seed + mustPass(t, client.Clusters().Create(context.Background(), clusters.Resource{ Comparable: clusters.Comparable{ - Name: "different-db", + Name: clusterName, Namespace: namespace, Storage: "256Mi", }, @@ -60,45 +66,63 @@ func TestPostgresIntegration(t *testing.T) { mustPass(t, client.Clients().Create(context.Background(), clients.Resource{ Comparable: clients.Comparable{ - Cluster: clients.Cluster{Name: "different-db", Namespace: namespace}, - Database: "new_db", - Name: "my-client", + Cluster: clients.Cluster{Name: clusterName, Namespace: namespace}, + Database: dbName, + Name: "my-client-" + seed, Namespace: namespace, Username: "my_user", - Secret: "my-secret", + Secret: "my-secret-" + seed, Owner: true, }, })) mustPass(t, client.Clients().Create(context.Background(), clients.Resource{ Comparable: clients.Comparable{ - Cluster: clients.Cluster{Name: "different-db", Namespace: namespace}, - Database: "new_db", - Name: "other-client", + Cluster: clients.Cluster{Name: clusterName, Namespace: namespace}, + Database: dbName, + Name: "other-client-" + seed, Namespace: namespace, Username: "other_user", - Secret: "other-secret", + Secret: "other-secret-" + seed, }, })) - secret := waitForResult(t, func() (secrets.Resource, error) { - return client.Secrets().Get(context.Background(), "my-secret", namespace) + owner := waitForResult(t, func() (secrets.Resource, error) { + return client.Secrets().Get(context.Background(), "my-secret-"+seed, namespace) + }) + + ownerPort, err := strconv.ParseInt(owner.GetPort(), 10, 0) + mustPass(t, err) + + mig, err := migrations.New(config.Config{ + Host: owner.GetHost(), + Port: int(ownerPort), + Username: owner.User, + Password: owner.Password, + Database: owner.Database, + }) + mustPass(t, err) + + mustPass(t, mig.Init()) + + user := waitForResult(t, func() (secrets.Resource, error) { + return client.Secrets().Get(context.Background(), "other-secret-"+seed, namespace) }) - port, err := strconv.ParseInt(secret.GetPort(), 10, 0) + userPort, err := strconv.ParseInt(user.GetPort(), 10, 0) mustPass(t, err) - pg, err := postgres_helpers.New(postgres.ConnectConfig{ - Host: secret.GetHost(), - Port: int(port), - Username: secret.User, - Password: secret.Password, - Database: secret.Database, + pg, err := postgres_helpers.New(config.Config{ + Host: user.GetHost(), + Port: int(userPort), + Username: user.User, + Password: user.Password, + Database: user.Database, }) mustPass(t, err) tables := pg.GetTableNames(t) - expected := []string{} + expected := []string{"migrations"} assert.Equal(t, expected, tables) } diff --git a/tests/tests.go b/tests/tests.go index cad58ac..eb23c16 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -2,6 +2,7 @@ package tests import ( "errors" + "math/rand" "testing" "time" @@ -95,3 +96,12 @@ func waitFor(f func() error) error { return err } } + +func randomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyz0123456789" + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} From 3ffb613839852c61069d08559f57ed678c85eb21 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 27 May 2024 14:59:28 +0100 Subject: [PATCH 20/22] cleanup --- pkg/k8s_generic/builder.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/k8s_generic/builder.go b/pkg/k8s_generic/builder.go index f64c9a6..d3dfa95 100644 --- a/pkg/k8s_generic/builder.go +++ b/pkg/k8s_generic/builder.go @@ -1,7 +1,6 @@ package k8s_generic import ( - "fmt" "os" "k8s.io/apimachinery/pkg/runtime/schema" @@ -24,9 +23,6 @@ func NewBuilder() (*Builder, error) { var err error if kubeconfig != "" { - if err != nil { - return nil, fmt.Errorf("failed to get current working directory: %+v", err) - } config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, err From f4bde45848674d3261ed11ea11ceb79254d69e53 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 27 May 2024 17:28:15 +0100 Subject: [PATCH 21/22] testing migrations --- .github/workflows/tags.yaml | 31 +++++++++++++++++++++++- cmd/migrations/main.go | 48 +++++++++++++++++++++++++++++++++++++ deploy/Dockerfile | 4 +++- justfile | 7 +++++- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 cmd/migrations/main.go diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml index cef3293..9162d62 100644 --- a/.github/workflows/tags.yaml +++ b/.github/workflows/tags.yaml @@ -22,7 +22,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.22' - name: Install dependencies run: go mod download - name: Build @@ -34,6 +34,35 @@ jobs: file: ./deploy/Dockerfile push: true tags: benwright/db-operator:${{ github.ref_name }} + migrations: + runs-on: ubuntu-latest + environment: production + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER_NAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + - name: Install dependencies + run: go mod download + - name: Build + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -v -o ./dist/mig ./cmd/migrations/main.go + - name: Image + uses: docker/build-push-action@v4 + with: + context: ./dist + file: ./deploy/Dockerfile + push: true + tags: benwright/db-pg-migrations:${{ github.ref_name }} chart: runs-on: ubuntu-latest environment: production diff --git a/cmd/migrations/main.go b/cmd/migrations/main.go new file mode 100644 index 0000000..6f4207a --- /dev/null +++ b/cmd/migrations/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "os" + + "github.com/benjamin-wright/db-operator/pkg/postgres/config" + "github.com/benjamin-wright/db-operator/pkg/postgres/migrations" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + path, ok := os.LookupEnv("POSTGRES_MIGRATIONS_PATH") + if !ok { + path = "/migrations" + } + + log.Info().Msgf("loading migrations from %s", path) + m, err := migrations.LoadMigrations(path) + if err != nil { + log.Fatal().Err(err).Msg("failed to load migrations") + } + + cfg, err := config.FromEnv() + if err != nil { + log.Fatal().Err(err).Msg("failed to load config from env") + } + + client, err := migrations.New(cfg) + if err != nil { + log.Fatal().Err(err).Msg("failed to create client") + } + + log.Info().Msg("initializing client") + err = client.Init() + if err != nil { + log.Fatal().Err(err).Msg("failed to init client") + } + + log.Info().Msg("running migrations") + err = client.Run(m) + if err != nil { + log.Fatal().Err(err).Msg("failed to run migrations") + } + + log.Info().Msg("migrations complete") +} diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 658520f..29f65dd 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,5 +1,7 @@ FROM scratch -COPY app /app +ARG BINARY_NAME + +COPY ${BINARY_NAME} /app ENTRYPOINT [ "/app" ] \ No newline at end of file diff --git a/justfile b/justfile index 44d9aa8..f221c79 100644 --- a/justfile +++ b/justfile @@ -50,7 +50,12 @@ int-test: build IMAGE_TAG: mkdir -p ./dist CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ./dist/app ./cmd/operator/main.go - docker build -t "{{IMAGE_TAG}}" -f deploy/Dockerfile ./dist + docker build -t "{{IMAGE_TAG}}" --build-arg BINARY_NAME=app -f deploy/Dockerfile ./dist + +build-mig IMAGE_TAG: + mkdir -p ./dist + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ./dist/mig ./cmd/migrations/main.go + docker build -t "{{IMAGE_TAG}}" --build-arg BINARY_NAME=mig -f deploy/Dockerfile ./dist build-test IMAGE_TAG: mkdir -p ./dist From 75a64d981796df0717411b1c288a2e1739a832d5 Mon Sep 17 00:00:00 2001 From: Ben Wright Date: Mon, 27 May 2024 17:41:12 +0100 Subject: [PATCH 22/22] tweaks --- deploy/Test.Dockerfile | 2 +- tests/nats_test.go | 7 +++++++ tests/postgres_test.go | 13 ++++++++++++- tests/redis_test.go | 7 +++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/deploy/Test.Dockerfile b/deploy/Test.Dockerfile index f3d1940..0b5d9de 100644 --- a/deploy/Test.Dockerfile +++ b/deploy/Test.Dockerfile @@ -3,4 +3,4 @@ FROM scratch COPY tests /tests ENTRYPOINT [ "/tests" ] -CMD [ "-test.run", "Integration", "-test.v" ] \ No newline at end of file +CMD [ "-test.run", "Integration" ] \ No newline at end of file diff --git a/tests/nats_test.go b/tests/nats_test.go index 7ced454..95d6c4c 100644 --- a/tests/nats_test.go +++ b/tests/nats_test.go @@ -8,6 +8,7 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s" "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/nats/k8s/clusters" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) @@ -16,6 +17,12 @@ func TestNatsIntegration(t *testing.T) { t.Skip("skipping integration test") } + if testing.Verbose() { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else { + zerolog.SetGlobalLevel(zerolog.Disabled) + } + namespace := os.Getenv("NAMESPACE") client, err := k8s.New() diff --git a/tests/postgres_test.go b/tests/postgres_test.go index 84fc69d..d1fbdd5 100644 --- a/tests/postgres_test.go +++ b/tests/postgres_test.go @@ -104,6 +104,17 @@ func TestPostgresIntegration(t *testing.T) { mustPass(t, err) mustPass(t, mig.Init()) + mustPass(t, mig.Run([]migrations.Migration{ + { + Index: 1, + Query: ` + CREATE TABLE test_table ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL + ) + `, + }, + })) user := waitForResult(t, func() (secrets.Resource, error) { return client.Secrets().Get(context.Background(), "other-secret-"+seed, namespace) @@ -123,6 +134,6 @@ func TestPostgresIntegration(t *testing.T) { tables := pg.GetTableNames(t) - expected := []string{"migrations"} + expected := []string{"migrations", "test_table"} assert.Equal(t, expected, tables) } diff --git a/tests/redis_test.go b/tests/redis_test.go index 6490877..614bb36 100644 --- a/tests/redis_test.go +++ b/tests/redis_test.go @@ -8,6 +8,7 @@ import ( "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s" "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clients" "github.com/benjamin-wright/db-operator/internal/dbs/redis/k8s/clusters" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) @@ -16,6 +17,12 @@ func TestRedisIntegration(t *testing.T) { t.Skip("skipping integration test") } + if testing.Verbose() { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else { + zerolog.SetGlobalLevel(zerolog.Disabled) + } + namespace := os.Getenv("NAMESPACE") client, err := k8s.New()