From 28365bb34bb231db04f8e474b2c50b43fbb04cd7 Mon Sep 17 00:00:00 2001 From: Ori Shoshan Date: Tue, 16 Apr 2024 01:41:06 +0300 Subject: [PATCH] Revert "Database enforcement support - new CRD and database permission configuration (#392)" This reverts commit cadfc8c9f98b9eabe780b2e009c50ceb635f6cd8. --- src/.gitignore | 2 +- src/go.mod | 3 - src/go.sum | 10 - src/operator/PROJECT | 12 - .../api/v1alpha2/zz_generated.deepcopy.go | 1 + .../v1alpha3/postgresqlserverconfig_types.go | 72 --- .../postgresqlserverconfig_webhook.go | 28 - .../api/v1alpha3/zz_generated.deepcopy.go | 146 +----- src/operator/config/crd/.yml | 1 - ...terize.com_postgresqlserverconfigs.patched | 82 --- ....otterize.com_postgresqlserverconfigs.yaml | 70 --- src/operator/config/crd/kustomization.yaml | 52 +- .../webhook_in_postgresqlserverconfig.yaml | 16 - src/operator/config/rbac/role.yaml | 23 - src/operator/config/webhook/manifests-patched | 20 - src/operator/config/webhook/manifests.yaml | 20 - .../controllers/intents_controller.go | 42 +- .../intents_reconcilers/database/database.go | 89 ++++ .../database/database_reconciler.go | 159 ------ .../database/database_reconciler_test.go | 156 ------ .../database/database_test.go | 187 +++++++ src/operator/copy-manifests-to-helm.sh | 7 +- src/operator/main.go | 70 +-- src/operator/otterizecrds/ensure.go | 9 - ...erverconfigs-customresourcedefinition.yaml | 82 --- .../postgresqlserverconfigs_webhook.go | 123 ----- src/shared/clusterutils/clusterid.go | 34 -- src/shared/clusterutils/username.go | 47 -- .../databaseconfigurator/postgres/postgres.go | 477 ------------------ .../databaseconfigurator/postgres/utils.go | 54 -- src/shared/local.env | 20 + src/shared/operator_cloud_client/cloud_api.go | 16 +- .../otterizecloud/graphqlclient/generated.go | 350 ++++++++----- .../graphqlclient/genqlient.graphql | 9 +- .../graphqlclient/schema.graphql | 56 +- .../otterizecloud/mocks/mock_cloud_api.go | 14 + .../telemetries/telemetriesgql/generated.go | 35 +- .../telemetries/telemetriesgql/schema.graphql | 58 ++- 38 files changed, 685 insertions(+), 1967 deletions(-) delete mode 100644 src/operator/api/v1alpha3/postgresqlserverconfig_types.go delete mode 100644 src/operator/api/v1alpha3/postgresqlserverconfig_webhook.go delete mode 100644 src/operator/config/crd/.yml delete mode 100644 src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.patched delete mode 100644 src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.yaml delete mode 100644 src/operator/config/crd/patches/webhook_in_postgresqlserverconfig.yaml create mode 100644 src/operator/controllers/intents_reconcilers/database/database.go delete mode 100644 src/operator/controllers/intents_reconcilers/database/database_reconciler.go delete mode 100644 src/operator/controllers/intents_reconcilers/database/database_reconciler_test.go create mode 100644 src/operator/controllers/intents_reconcilers/database/database_test.go delete mode 100644 src/operator/otterizecrds/postgresqlserverconfigs-customresourcedefinition.yaml delete mode 100644 src/operator/webhooks/postgresqlserverconfigs_webhook.go delete mode 100644 src/shared/clusterutils/clusterid.go delete mode 100644 src/shared/clusterutils/username.go delete mode 100644 src/shared/databaseconfigurator/postgres/postgres.go delete mode 100644 src/shared/databaseconfigurator/postgres/utils.go create mode 100644 src/shared/local.env diff --git a/src/.gitignore b/src/.gitignore index 3848e324b..feb504f1b 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -22,4 +22,4 @@ testbin/* .idea *.swp *.swo -*~ \ No newline at end of file +*~ diff --git a/src/go.mod b/src/go.mod index b94d680a2..65ba99993 100644 --- a/src/go.mod +++ b/src/go.mod @@ -32,7 +32,6 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.5.0 - github.com/jackc/pgx/v5 v5.5.5 github.com/otterize/lox v0.0.0-20220525164329-9ca2bf91c3dd github.com/prometheus/client_golang v1.18.0 github.com/samber/lo v1.33.0 @@ -97,8 +96,6 @@ require ( github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.15 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.0.0 // indirect diff --git a/src/go.sum b/src/go.sum index a53df99b2..40707e375 100644 --- a/src/go.sum +++ b/src/go.sum @@ -319,14 +319,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -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/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/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -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/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -650,8 +642,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/src/operator/PROJECT b/src/operator/PROJECT index e9f08c8f9..f44d09825 100644 --- a/src/operator/PROJECT +++ b/src/operator/PROJECT @@ -82,16 +82,4 @@ resources: webhooks: conversion: true webhookVersion: v1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: k8s.otterize.com - group: otterize - kind: PostgreSQLServerConfig - path: github.com/otterize/intents-operator/api/v1alpha3 - version: v1alpha3 - webhooks: - validation: true - webhookVersion: v1 version: "3" diff --git a/src/operator/api/v1alpha2/zz_generated.deepcopy.go b/src/operator/api/v1alpha2/zz_generated.deepcopy.go index b2413bcab..09161ec64 100644 --- a/src/operator/api/v1alpha2/zz_generated.deepcopy.go +++ b/src/operator/api/v1alpha2/zz_generated.deepcopy.go @@ -1,4 +1,5 @@ //go:build !ignore_autogenerated +// +build !ignore_autogenerated /* Copyright 2022. diff --git a/src/operator/api/v1alpha3/postgresqlserverconfig_types.go b/src/operator/api/v1alpha3/postgresqlserverconfig_types.go deleted file mode 100644 index 803069aab..000000000 --- a/src/operator/api/v1alpha3/postgresqlserverconfig_types.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type DBPermissionChange string - -const ( - DBPermissionChangeApply DBPermissionChange = "APPLY" - DBPermissionChangeDelete DBPermissionChange = "DELETE" -) - -type DatabaseCredentials struct { - Username string `json:"username"` - Password string `json:"password"` -} - -// PostgreSQLServerConfigSpec defines the desired state of PostgreSQLServerConfig -type PostgreSQLServerConfigSpec struct { - DatabaseName string `json:"databaseName"` - Address string `json:"address"` - Credentials DatabaseCredentials `json:"credentials"` -} - -// PostgreSQLServerConfigStatus defines the observed state of PostgreSQLServerConfig -type PostgreSQLServerConfigStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status -//+kubebuilder:storageversion - -// PostgreSQLServerConfig is the Schema for the databaseserverconfig API -type PostgreSQLServerConfig struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec PostgreSQLServerConfigSpec `json:"spec,omitempty"` - Status PostgreSQLServerConfigStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// PostgreSQLServerConfigList contains a list of PostgreSQLServerConfig -type PostgreSQLServerConfigList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []PostgreSQLServerConfig `json:"items"` -} - -func init() { - SchemeBuilder.Register(&PostgreSQLServerConfig{}, &PostgreSQLServerConfigList{}) -} diff --git a/src/operator/api/v1alpha3/postgresqlserverconfig_webhook.go b/src/operator/api/v1alpha3/postgresqlserverconfig_webhook.go deleted file mode 100644 index f5031baef..000000000 --- a/src/operator/api/v1alpha3/postgresqlserverconfig_webhook.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha3 - -import ( - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func (in *PostgreSQLServerConfig) SetupWebhookWithManager(mgr ctrl.Manager, validator webhook.CustomValidator) error { - return ctrl.NewWebhookManagedBy(mgr). - For(in).WithValidator(validator). - Complete() -} diff --git a/src/operator/api/v1alpha3/zz_generated.deepcopy.go b/src/operator/api/v1alpha3/zz_generated.deepcopy.go index 8460e61df..0899a3e4f 100644 --- a/src/operator/api/v1alpha3/zz_generated.deepcopy.go +++ b/src/operator/api/v1alpha3/zz_generated.deepcopy.go @@ -1,4 +1,5 @@ //go:build !ignore_autogenerated +// +build !ignore_autogenerated /* Copyright 2022. @@ -24,41 +25,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureKeyVaultPolicy) DeepCopyInto(out *AzureKeyVaultPolicy) { - *out = *in - if in.CertificatePermissions != nil { - in, out := &in.CertificatePermissions, &out.CertificatePermissions - *out = make([]AzureKeyVaultCertificatePermission, len(*in)) - copy(*out, *in) - } - if in.KeyPermissions != nil { - in, out := &in.KeyPermissions, &out.KeyPermissions - *out = make([]AzureKeyVaultKeyPermission, len(*in)) - copy(*out, *in) - } - if in.SecretPermissions != nil { - in, out := &in.SecretPermissions, &out.SecretPermissions - *out = make([]AzureKeyVaultSecretPermission, len(*in)) - copy(*out, *in) - } - if in.StoragePermissions != nil { - in, out := &in.StoragePermissions, &out.StoragePermissions - *out = make([]AzureKeyVaultStoragePermission, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKeyVaultPolicy. -func (in *AzureKeyVaultPolicy) DeepCopy() *AzureKeyVaultPolicy { - if in == nil { - return nil - } - out := new(AzureKeyVaultPolicy) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientIntents) DeepCopyInto(out *ClientIntents) { *out = *in @@ -122,21 +88,6 @@ func (in *ClientIntentsList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseCredentials) DeepCopyInto(out *DatabaseCredentials) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseCredentials. -func (in *DatabaseCredentials) DeepCopy() *DatabaseCredentials { - if in == nil { - return nil - } - out := new(DatabaseCredentials) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DatabaseResource) DeepCopyInto(out *DatabaseResource) { *out = *in @@ -216,11 +167,6 @@ func (in *Intent) DeepCopyInto(out *Intent) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.AzureKeyVaultPolicy != nil { - in, out := &in.AzureKeyVaultPolicy, &out.AzureKeyVaultPolicy - *out = new(AzureKeyVaultPolicy) - (*in).DeepCopyInto(*out) - } if in.Internet != nil { in, out := &in.Internet, &out.Internet *out = new(Internet) @@ -429,96 +375,6 @@ func (in *KafkaTopic) DeepCopy() *KafkaTopic { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSQLServerConfig) DeepCopyInto(out *PostgreSQLServerConfig) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLServerConfig. -func (in *PostgreSQLServerConfig) DeepCopy() *PostgreSQLServerConfig { - if in == nil { - return nil - } - out := new(PostgreSQLServerConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PostgreSQLServerConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSQLServerConfigList) DeepCopyInto(out *PostgreSQLServerConfigList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PostgreSQLServerConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLServerConfigList. -func (in *PostgreSQLServerConfigList) DeepCopy() *PostgreSQLServerConfigList { - if in == nil { - return nil - } - out := new(PostgreSQLServerConfigList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PostgreSQLServerConfigList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSQLServerConfigSpec) DeepCopyInto(out *PostgreSQLServerConfigSpec) { - *out = *in - out.Credentials = in.Credentials -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLServerConfigSpec. -func (in *PostgreSQLServerConfigSpec) DeepCopy() *PostgreSQLServerConfigSpec { - if in == nil { - return nil - } - out := new(PostgreSQLServerConfigSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSQLServerConfigStatus) DeepCopyInto(out *PostgreSQLServerConfigStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLServerConfigStatus. -func (in *PostgreSQLServerConfigStatus) DeepCopy() *PostgreSQLServerConfigStatus { - if in == nil { - return nil - } - out := new(PostgreSQLServerConfigStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProtectedService) DeepCopyInto(out *ProtectedService) { *out = *in diff --git a/src/operator/config/crd/.yml b/src/operator/config/crd/.yml deleted file mode 100644 index 8b1378917..000000000 --- a/src/operator/config/crd/.yml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.patched b/src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.patched deleted file mode 100644 index c5a0c4cbe..000000000 --- a/src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.patched +++ /dev/null @@ -1,82 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.14.0 - helm.sh/resource-policy: keep - creationTimestamp: null - labels: - app.kubernetes.io/part-of: otterize - name: postgresqlserverconfigs.k8s.otterize.com -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: intents-operator-webhook-service - namespace: otterize-system - path: /convert - conversionReviewVersions: - - v1 - group: k8s.otterize.com - names: - kind: PostgreSQLServerConfig - listKind: PostgreSQLServerConfigList - plural: postgresqlserverconfigs - singular: postgresqlserverconfig - scope: Namespaced - versions: - - name: v1alpha3 - schema: - openAPIV3Schema: - description: PostgreSQLServerConfig is the Schema for the databaseserverconfig API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: PostgreSQLServerConfigSpec defines the desired state of PostgreSQLServerConfig - properties: - address: - type: string - credentials: - properties: - password: - type: string - username: - type: string - required: - - password - - username - type: object - databaseName: - type: string - required: - - address - - credentials - - databaseName - type: object - status: - description: PostgreSQLServerConfigStatus defines the observed state of PostgreSQLServerConfig - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.yaml b/src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.yaml deleted file mode 100644 index 3926f0bcb..000000000 --- a/src/operator/config/crd/k8s.otterize.com_postgresqlserverconfigs.yaml +++ /dev/null @@ -1,70 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.14.0 - name: postgresqlserverconfigs.k8s.otterize.com -spec: - group: k8s.otterize.com - names: - kind: PostgreSQLServerConfig - listKind: PostgreSQLServerConfigList - plural: postgresqlserverconfigs - singular: postgresqlserverconfig - scope: Namespaced - versions: - - name: v1alpha3 - schema: - openAPIV3Schema: - description: PostgreSQLServerConfig is the Schema for the databaseserverconfig - API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: PostgreSQLServerConfigSpec defines the desired state of PostgreSQLServerConfig - properties: - address: - type: string - credentials: - properties: - password: - type: string - username: - type: string - required: - - password - - username - type: object - databaseName: - type: string - required: - - address - - credentials - - databaseName - type: object - status: - description: PostgreSQLServerConfigStatus defines the observed state of - PostgreSQLServerConfig - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/src/operator/config/crd/kustomization.yaml b/src/operator/config/crd/kustomization.yaml index 8f4e23feb..a773fd829 100644 --- a/src/operator/config/crd/kustomization.yaml +++ b/src/operator/config/crd/kustomization.yaml @@ -5,11 +5,14 @@ resources: - k8s.otterize.com_clientintents.yaml - k8s.otterize.com_kafkaserverconfigs.yaml - k8s.otterize.com_protectedservices.yaml -- k8s.otterize.com_postgresqlserverconfigs.yaml #+kubebuilder:scaffold:crdkustomizeresource +patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD +- patches/webhook_in_clientintents.yaml +- patches/webhook_in_kafkaserverconfig.yaml +- patches/webhook_in_protectedservice.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -21,37 +24,30 @@ resources: configurations: - kustomizeconfig.yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -labels: -- includeSelectors: true - pairs: - app.kubernetes.io/part-of: otterize +commonLabels: + app.kubernetes.io/part-of: otterize + patches: -- path: patches/webhook_in_clientintents.yaml -- path: patches/webhook_in_kafkaserverconfig.yaml -- path: patches/webhook_in_protectedservice.yaml -- path: patches/webhook_in_postgresqlserverconfig.yaml -- patch: |- + - patch: |- - op: replace path: /spec/conversion/webhook/clientConfig/service/namespace value: otterize-system - op: replace path: /spec/conversion/webhook/clientConfig/service/name value: intents-operator-webhook-service - target: - kind: CustomResourceDefinition -- patch: |- - - op: add - path: /metadata/annotations - value: - controller-gen.kubebuilder.io/version: v0.14.0 - helm.sh/resource-policy: keep - target: - kind: CustomResourceDefinition -- patch: |- - - op: add - path: /metadata/creationTimestamp - value: null - target: - kind: CustomResourceDefinition + target: + kind: CustomResourceDefinition + - patch: |- + - op: add + path: /metadata/annotations + value: + controller-gen.kubebuilder.io/version: v0.14.0 + helm.sh/resource-policy: keep + target: + kind: CustomResourceDefinition + - patch: |- + - op: add + path: /metadata/creationTimestamp + value: null + target: + kind: CustomResourceDefinition diff --git a/src/operator/config/crd/patches/webhook_in_postgresqlserverconfig.yaml b/src/operator/config/crd/patches/webhook_in_postgresqlserverconfig.yaml deleted file mode 100644 index b97fa653d..000000000 --- a/src/operator/config/crd/patches/webhook_in_postgresqlserverconfig.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: postgresqlserverconfigs.k8s.otterize.com -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/src/operator/config/rbac/role.yaml b/src/operator/config/rbac/role.yaml index 9b6d2b089..e9e668922 100644 --- a/src/operator/config/rbac/role.yaml +++ b/src/operator/config/rbac/role.yaml @@ -4,17 +4,6 @@ kind: ClusterRole metadata: name: otterize-intents-operator-manager-role rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - create - - get - - list - - patch - - update - - watch - apiGroups: - "" resources: @@ -177,18 +166,6 @@ rules: - get - patch - update -- apiGroups: - - k8s.otterize.com - resources: - - postgresqlserverconfigs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - k8s.otterize.com resources: diff --git a/src/operator/config/webhook/manifests-patched b/src/operator/config/webhook/manifests-patched index 02101282e..8ec2f2ca6 100644 --- a/src/operator/config/webhook/manifests-patched +++ b/src/operator/config/webhook/manifests-patched @@ -46,26 +46,6 @@ webhooks: resources: - clientintents sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: intents-operator-webhook-service - namespace: {{ .Release.Namespace }} - path: /validate-k8s-otterize-com-v1alpha3-postgresqlserverconfig - failurePolicy: Fail - name: postgresqlserverconfig.kb.io - rules: - - apiGroups: - - k8s.otterize.com - apiVersions: - - v1alpha3 - operations: - - CREATE - - UPDATE - resources: - - postgresqlserverconfig - sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/src/operator/config/webhook/manifests.yaml b/src/operator/config/webhook/manifests.yaml index a7a080c74..f64581da6 100644 --- a/src/operator/config/webhook/manifests.yaml +++ b/src/operator/config/webhook/manifests.yaml @@ -44,26 +44,6 @@ webhooks: resources: - clientintents sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-k8s-otterize-com-v1alpha3-postgresqlserverconfig - failurePolicy: Fail - name: postgresqlserverconfig.kb.io - rules: - - apiGroups: - - k8s.otterize.com - apiVersions: - - v1alpha3 - operations: - - CREATE - - UPDATE - resources: - - postgresqlserverconfig - sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/src/operator/controllers/intents_controller.go b/src/operator/controllers/intents_controller.go index 3c0603f57..c3fbae6f5 100644 --- a/src/operator/controllers/intents_controller.go +++ b/src/operator/controllers/intents_controller.go @@ -118,7 +118,7 @@ func NewIntentsReconciler( } if enforcementConfig.EnableDatabasePolicy { - databaseReconciler := database.NewDatabaseReconciler(client, scheme) + databaseReconciler := database.NewDatabaseReconciler(client, scheme, otterizeClient) intentsReconciler.group.AddToGroup(databaseReconciler) } @@ -126,15 +126,12 @@ func NewIntentsReconciler( } //+kubebuilder:rbac:groups=k8s.otterize.com,resources=clientintents,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=k8s.otterize.com,resources=postgresqlserverconfigs,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=k8s.otterize.com,resources=clientintents/status,verbs=get;update;patch //+kubebuilder:rbac:groups=k8s.otterize.com,resources=clientintents/finalizers,verbs=update //+kubebuilder:rbac:groups="",resources=pods,verbs=get;update;patch;list;watch -//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;update;patch;list;watch;create //+kubebuilder:rbac:groups="networking.k8s.io",resources=networkpolicies,verbs=get;update;patch;list;watch;delete;create //+kubebuilder:rbac:groups="admissionregistration.k8s.io",resources=validatingwebhookconfigurations,verbs=get;update;patch;list //+kubebuilder:rbac:groups="apiextensions.k8s.io",resources=customresourcedefinitions,verbs=get;list;watch;update;create;patch -//+kubebuilder:rbac:groups="apiextensions.k8s.io",resources=customresourcedefinitions,verbs=get;list;watch;update;create;patch // +kubebuilder:rbac:groups=iam.cnrm.cloud.google.com,resources=iampartialpolicies,verbs=get;list;watch;create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to @@ -149,6 +146,7 @@ func (r *IntentsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } return ctrl.Result{}, errors.Wrap(err) } + if intents.Status.UpToDate != false && intents.Status.ObservedGeneration != intents.Generation { intentsCopy := intents.DeepCopy() intentsCopy.Status.UpToDate = false @@ -184,7 +182,6 @@ func (r *IntentsReconciler) SetupWithManager(mgr ctrl.Manager) error { WithOptions(controller.Options{RecoverPanic: lo.ToPtr(true)}). Watches(&otterizev1alpha3.ProtectedService{}, handler.EnqueueRequestsFromMapFunc(r.mapProtectedServiceToClientIntents)). Watches(&corev1.Endpoints{}, handler.EnqueueRequestsFromMapFunc(r.watchApiServerEndpoint)). - Watches(&otterizev1alpha3.PostgreSQLServerConfig{}, handler.EnqueueRequestsFromMapFunc(r.mapPostgresInstanceNameToDatabaseIntents)). Complete(r) if err != nil { return errors.Wrap(err) @@ -231,38 +228,6 @@ func (r *IntentsReconciler) mapProtectedServiceToClientIntents(ctx context.Conte return r.mapIntentsToRequests(intentsToReconcile) } -func (r *IntentsReconciler) mapPostgresInstanceNameToDatabaseIntents(_ context.Context, obj client.Object) []reconcile.Request { - pgServerConf := obj.(*otterizev1alpha3.PostgreSQLServerConfig) - logrus.Infof("Enqueueing client intents for PostgreSQLServerConfig change %s", pgServerConf.Name) - - intentsToReconcile := r.getIntentsToPostgresInstance(pgServerConf) - - requests := make([]reconcile.Request, 0) - for _, clientIntents := range intentsToReconcile { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: clientIntents.Name, - Namespace: clientIntents.Namespace, - }, - }) - } - return requests -} - -func (r *IntentsReconciler) getIntentsToPostgresInstance(pgServerConf *otterizev1alpha3.PostgreSQLServerConfig) []otterizev1alpha3.ClientIntents { - intentsList := otterizev1alpha3.ClientIntentsList{} - dbInstanceName := pgServerConf.Name - err := r.client.List(context.Background(), - &intentsList, - &client.MatchingFields{otterizev1alpha3.OtterizeTargetServerIndexField: dbInstanceName}, - ) - if err != nil { - logrus.Errorf("Failed to list client intents targeting %s: %v", dbInstanceName, err) - } - - return intentsList.Items -} - func (r *IntentsReconciler) mapIntentsToRequests(intentsToReconcile []otterizev1alpha3.ClientIntents) []reconcile.Request { requests := make([]reconcile.Request, 0) for _, clientIntents := range intentsToReconcile { @@ -312,9 +277,6 @@ func (r *IntentsReconciler) InitIntentsServerIndices(mgr ctrl.Manager) error { if !intent.IsTargetServerKubernetesService() { res = append(res, intent.GetServerFullyQualifiedName(intents.Namespace)) } - if intent.DatabaseResources != nil { - res = append(res, intent.GetTargetServerName()) - } fullyQualifiedSvcName, ok := intent.GetK8sServiceFullyQualifiedName(intents.Namespace) if ok { res = append(res, fullyQualifiedSvcName) diff --git a/src/operator/controllers/intents_reconcilers/database/database.go b/src/operator/controllers/intents_reconcilers/database/database.go new file mode 100644 index 000000000..428cee4bf --- /dev/null +++ b/src/operator/controllers/intents_reconcilers/database/database.go @@ -0,0 +1,89 @@ +package database + +import ( + "context" + otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" + "github.com/otterize/intents-operator/src/shared/errors" + "github.com/otterize/intents-operator/src/shared/injectablerecorder" + "github.com/otterize/intents-operator/src/shared/operator_cloud_client" + "github.com/otterize/intents-operator/src/shared/otterizecloud/graphqlclient" + "github.com/sirupsen/logrus" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + ReasonApplyingDatabaseIntentsFailed = "ApplyingDatabaseIntentsFailed" + ReasonAppliedDatabaseIntents = "AppliedDatabaseIntents" +) + +type DatabaseReconciler struct { + client client.Client + scheme *runtime.Scheme + otterizeClient operator_cloud_client.CloudClient + injectablerecorder.InjectableRecorder +} + +func NewDatabaseReconciler( + client client.Client, + scheme *runtime.Scheme, + otterizeClient operator_cloud_client.CloudClient, +) *DatabaseReconciler { + return &DatabaseReconciler{ + client: client, + scheme: scheme, + otterizeClient: otterizeClient, + } +} + +func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + intents := &otterizev1alpha3.ClientIntents{} + logger := logrus.WithField("namespacedName", req.String()) + err := r.client.Get(ctx, req.NamespacedName, intents) + if err != nil && k8serrors.IsNotFound(err) { + logger.Info("No intents found") + return ctrl.Result{}, nil + } else if err != nil { + return ctrl.Result{}, errors.Wrap(err) + } + + if intents.Spec == nil { + logger.Info("No specs found") + return ctrl.Result{}, nil + } + + action := graphqlclient.DBPermissionChangeApply + if !intents.ObjectMeta.DeletionTimestamp.IsZero() { + action = graphqlclient.DBPermissionChangeDelete + } + + var intentInputList []graphqlclient.IntentInput + for _, intent := range intents.GetCallsList() { + if intent.Type != otterizev1alpha3.IntentTypeDatabase { + continue + } + + intentInput := intent.ConvertToCloudFormat(intents.Namespace, intents.GetServiceName()) + intentInputList = append(intentInputList, intentInput) + } + + if len(intentInputList) == 0 { + return ctrl.Result{}, nil + } + + if err := r.otterizeClient.ApplyDatabaseIntent(ctx, intentInputList, action); err != nil { + errType, errMsg, ok := graphqlclient.GetGraphQLUserError(err) + if !ok || errType != graphqlclient.UserErrorTypeAppliedIntentsError { + r.RecordWarningEventf(intents, ReasonApplyingDatabaseIntentsFailed, "Failed applying database intents: %s", err.Error()) + return ctrl.Result{}, errors.Wrap(err) + } + r.RecordWarningEventf(intents, ReasonApplyingDatabaseIntentsFailed, "Failed applying database intents: %s", errMsg) + return ctrl.Result{}, errors.Wrap(err) + } + + r.RecordNormalEventf(intents, ReasonAppliedDatabaseIntents, "Database intents reconcile complete, reconciled %d intent calls", len(intentInputList)) + + return ctrl.Result{}, nil +} diff --git a/src/operator/controllers/intents_reconcilers/database/database_reconciler.go b/src/operator/controllers/intents_reconcilers/database/database_reconciler.go deleted file mode 100644 index 0b5573a1d..000000000 --- a/src/operator/controllers/intents_reconcilers/database/database_reconciler.go +++ /dev/null @@ -1,159 +0,0 @@ -package database - -import ( - "context" - "fmt" - "github.com/jackc/pgx/v5" - otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" - "github.com/otterize/intents-operator/src/shared/clusterutils" - "github.com/otterize/intents-operator/src/shared/databaseconfigurator/postgres" - "github.com/otterize/intents-operator/src/shared/errors" - "github.com/otterize/intents-operator/src/shared/injectablerecorder" - "github.com/samber/lo" - "github.com/sirupsen/logrus" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - ReasonApplyingDatabaseIntentsFailed = "ApplyingDatabaseIntentsFailed" - ReasonAppliedDatabaseIntents = "AppliedDatabaseIntents" - ReasonErrorFetchingPostgresServerConfig = "ErrorFetchingPostgresServerConfig" - ReasonMissingPostgresServerConfig = "MissingPostgresServerConfig" -) - -type DatabaseReconciler struct { - client client.Client - scheme *runtime.Scheme - injectablerecorder.InjectableRecorder -} - -func NewDatabaseReconciler( - client client.Client, - scheme *runtime.Scheme, -) *DatabaseReconciler { - return &DatabaseReconciler{ - client: client, - scheme: scheme, - } -} - -func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - clientIntents := &otterizev1alpha3.ClientIntents{} - logger := logrus.WithField("namespacedName", req.String()) - err := r.client.Get(ctx, req.NamespacedName, clientIntents) - if err != nil && k8serrors.IsNotFound(err) { - logger.Info("No client intents found") - return ctrl.Result{}, nil - } else if err != nil { - return ctrl.Result{}, errors.Wrap(err) - } - - if clientIntents.Spec == nil { - logger.Info("No specs found") - return ctrl.Result{}, nil - } - - action := otterizev1alpha3.DBPermissionChangeApply - if !clientIntents.ObjectMeta.DeletionTimestamp.IsZero() { - action = otterizev1alpha3.DBPermissionChangeDelete - } - - dbIntents := lo.Filter(clientIntents.GetCallsList(), func(intent otterizev1alpha3.Intent, _ int) bool { - return intent.Type == otterizev1alpha3.IntentTypeDatabase - }) - - dbInstanceToIntents := lo.GroupBy(dbIntents, func(intent otterizev1alpha3.Intent) string { - return intent.Name // "Name" is the db instance name in our case. - }) - pgServerConfigs := otterizev1alpha3.PostgreSQLServerConfigList{} - err = r.client.List(ctx, &pgServerConfigs) - if err != nil { - r.RecordWarningEventf(clientIntents, ReasonErrorFetchingPostgresServerConfig, - "Error listing PostgresServerConfings. Error: %s", err.Error()) - return ctrl.Result{}, errors.Wrap(err) - } - - for databaseInstance, intents := range dbInstanceToIntents { - pgServerConf, err := findMatchingPGServerConfForDBInstance(databaseInstance, pgServerConfigs) - if err != nil { - r.RecordWarningEventf(clientIntents, ReasonMissingPostgresServerConfig, - "Could not find matching PostgreSQLServerConfig. Error: %s", err.Error()) - return ctrl.Result{}, nil // Not returning error on purpose, missing PGServerConf - record event and move on - } - pgConfigurator := postgres.NewPostgresConfigurator(pgServerConf.Spec) - err = pgConfigurator.ConfigureDBFromIntents(ctx, clientIntents.GetServiceName(), clientIntents.Namespace, intents, action) - if err != nil { - r.RecordWarningEventf(clientIntents, ReasonApplyingDatabaseIntentsFailed, - "Failed applying database clientIntents: %s", err.Error()) - return ctrl.Result{}, errors.Wrap(err) - } - } - - if err := r.cleanExcessPermissions(ctx, clientIntents, pgServerConfigs); err != nil { - return ctrl.Result{}, errors.Wrap(err) - } - - r.RecordNormalEventf(clientIntents, ReasonAppliedDatabaseIntents, "Database clientIntents reconcile complete, reconciled %d intent calls", len(dbIntents)) - - return ctrl.Result{}, nil -} - -// cleanExcessPermissions compensates for DB resources completely removed from client intents -// Permission edits are handled by the normal flow because we run "revoke all" before adding permissions -// This is only used when permissions might have been completely removed in a ClientIntents edit operation -func (r *DatabaseReconciler) cleanExcessPermissions(ctx context.Context, intents *otterizev1alpha3.ClientIntents, pgServerConfigs otterizev1alpha3.PostgreSQLServerConfigList) error { - clusterID, err := clusterutils.GetClusterUID(ctx) - if err != nil { - return err - } - - username := clusterutils.BuildHashedUsername(intents.GetServiceName(), intents.Namespace, clusterID) - pgUsername := clusterutils.KubernetesToPostgresName(username) - for _, config := range pgServerConfigs.Items { - pgConfigurator := postgres.NewPostgresConfigurator(config.Spec) - if err := pgConfigurator.SetConnection(ctx, config.Spec.DatabaseName); err != nil { - return errors.Wrap(err) - } - exists, err := pgConfigurator.ValidateUserExists(ctx, pgUsername) - if err != nil { - return errors.Wrap(err) - } - if !exists { - // User was never in the db, nothing more to do - continue - } - intent, found := lo.Find(intents.Spec.Calls, func(intent otterizev1alpha3.Intent) bool { - return intent.Name == config.Name - }) - if !found || intent.DatabaseResources == nil { - // Username exists in the database, but doesn't have any intents for it, run "revoke all" just in case - revokeBatch := &pgx.Batch{} - if err := pgConfigurator.QueueRevokePermissionsByDatabaseNameStatements(ctx, revokeBatch, pgUsername); err != nil { - return errors.Wrap(err) - } - if err := pgConfigurator.SendBatch(ctx, revokeBatch); err != nil { - return errors.Wrap(err) - } - } - } - return nil -} - -func findMatchingPGServerConfForDBInstance( - databaseInstanceName string, - pgServerConfigList otterizev1alpha3.PostgreSQLServerConfigList) (*otterizev1alpha3.PostgreSQLServerConfig, error) { - - matchingConf, found := lo.Find(pgServerConfigList.Items, func(conf otterizev1alpha3.PostgreSQLServerConfig) bool { - return databaseInstanceName == conf.Name - }) - - if !found { - return nil, errors.Wrap(fmt.Errorf( - "did not find Postgres server config to match database '%s' in the cluster", databaseInstanceName)) - } - - return &matchingConf, nil -} diff --git a/src/operator/controllers/intents_reconcilers/database/database_reconciler_test.go b/src/operator/controllers/intents_reconcilers/database/database_reconciler_test.go deleted file mode 100644 index faae52a08..000000000 --- a/src/operator/controllers/intents_reconcilers/database/database_reconciler_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package database - -import ( - "context" - "fmt" - otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" - mocks "github.com/otterize/intents-operator/src/operator/controllers/intents_reconcilers/mocks" - "github.com/otterize/intents-operator/src/shared/testbase" - "github.com/stretchr/testify/suite" - "go.uber.org/mock/gomock" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "testing" -) - -const ( - testNamespace string = "test-namespace" - intentsObjectName string = "test-client-intents" - clientName string = "test-client" - databaseInstance string = "db-instance" - tableName string = "test-table" - dbName string = "testdb" - dbAddress string = "https://test.this.db:5432" -) - -type DatabaseReconcilerTestSuite struct { - testbase.MocksSuiteBase - Reconciler *DatabaseReconciler - client *mocks.MockClient - namespacedName types.NamespacedName -} - -func (s *DatabaseReconcilerTestSuite) SetupTest() { - s.Controller = gomock.NewController(s.T()) - s.client = mocks.NewMockClient(s.Controller) - s.Reconciler = NewDatabaseReconciler( - s.client, - &runtime.Scheme{}, - ) - - s.Recorder = record.NewFakeRecorder(100) - s.Reconciler.Recorder = s.Recorder - - s.namespacedName = types.NamespacedName{ - Namespace: testNamespace, - Name: intentsObjectName, - } -} - -func (s *DatabaseReconcilerTestSuite) TestPGServerConfNotMatching() { - clientIntents := otterizev1alpha3.ClientIntents{ - ObjectMeta: metav1.ObjectMeta{ - Name: intentsObjectName, - Namespace: testNamespace, - }, - - Spec: &otterizev1alpha3.IntentsSpec{ - Service: otterizev1alpha3.Service{ - Name: clientName, - }, - Calls: []otterizev1alpha3.Intent{ - { - Name: databaseInstance, - Type: otterizev1alpha3.IntentTypeDatabase, - DatabaseResources: []otterizev1alpha3.DatabaseResource{{ - DatabaseName: dbName, - Table: tableName, - Operations: []otterizev1alpha3.DatabaseOperation{ - otterizev1alpha3.DatabaseOperationSelect, - otterizev1alpha3.DatabaseOperationInsert, - }, - }}, - }, - }, - }, - } - - pgServerConf := otterizev1alpha3.PostgreSQLServerConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-other", databaseInstance), - Namespace: testNamespace, - }, - Spec: otterizev1alpha3.PostgreSQLServerConfigSpec{ - DatabaseName: dbName, - Address: dbAddress, - Credentials: otterizev1alpha3.DatabaseCredentials{ - Username: "shhhhh", - Password: "secret", - }, - }, - } - - _, err := s.reconcileWithExpectedResources(clientIntents, pgServerConf) - s.Require().NoError(err) // Although no PGServerConf, we don't return error - just record an event - s.Require().Empty(ctrl.Result{}) - s.ExpectEvent(ReasonMissingPostgresServerConfig) -} - -func (s *DatabaseReconcilerTestSuite) TestNoPGServerConf() { - clientIntents := otterizev1alpha3.ClientIntents{ - ObjectMeta: metav1.ObjectMeta{ - Name: intentsObjectName, - Namespace: testNamespace, - }, - - Spec: &otterizev1alpha3.IntentsSpec{ - Service: otterizev1alpha3.Service{ - Name: clientName, - }, - Calls: []otterizev1alpha3.Intent{ - { - Name: databaseInstance, - Type: otterizev1alpha3.IntentTypeDatabase, - DatabaseResources: []otterizev1alpha3.DatabaseResource{{ - DatabaseName: dbName, - Table: tableName, - Operations: []otterizev1alpha3.DatabaseOperation{ - otterizev1alpha3.DatabaseOperationSelect, - otterizev1alpha3.DatabaseOperationInsert, - }, - }}, - }, - }, - }} - - _, err := s.reconcileWithExpectedResources(clientIntents, otterizev1alpha3.PostgreSQLServerConfig{}) - s.Require().NoError(err) // Although no PGServerConf, we don't return error - just record an event - s.Require().Empty(ctrl.Result{}) - s.ExpectEvent(ReasonMissingPostgresServerConfig) -} - -func (s *DatabaseReconcilerTestSuite) reconcileWithExpectedResources(clientIntents otterizev1alpha3.ClientIntents, pgServerConfig otterizev1alpha3.PostgreSQLServerConfig) (ctrl.Result, error) { - s.client.EXPECT().Get(gomock.Any(), gomock.Eq(s.namespacedName), gomock.Eq(&otterizev1alpha3.ClientIntents{})).DoAndReturn( - func(ctx context.Context, name types.NamespacedName, intents *otterizev1alpha3.ClientIntents, options ...client.ListOption) error { - clientIntents.DeepCopyInto(intents) - return nil - }) - - s.client.EXPECT().List(gomock.Any(), gomock.Eq(&otterizev1alpha3.PostgreSQLServerConfigList{}), gomock.Any()).DoAndReturn( - func(ctx context.Context, pgServerConfList *otterizev1alpha3.PostgreSQLServerConfigList, options ...client.ListOption) error { - pgServerConfList.Items = []otterizev1alpha3.PostgreSQLServerConfig{pgServerConfig} - return nil - }) - - req := ctrl.Request{NamespacedName: s.namespacedName} - - return s.Reconciler.Reconcile(context.Background(), req) -} - -func TestDatabaseReconcilerTestSuite(t *testing.T) { - suite.Run(t, new(DatabaseReconcilerTestSuite)) -} diff --git a/src/operator/controllers/intents_reconcilers/database/database_test.go b/src/operator/controllers/intents_reconcilers/database/database_test.go new file mode 100644 index 000000000..4ec31adb6 --- /dev/null +++ b/src/operator/controllers/intents_reconcilers/database/database_test.go @@ -0,0 +1,187 @@ +package database + +import ( + "context" + otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" + mocks "github.com/otterize/intents-operator/src/operator/controllers/intents_reconcilers/mocks" + "github.com/otterize/intents-operator/src/shared/otterizecloud/graphqlclient" + otterizecloudmocks "github.com/otterize/intents-operator/src/shared/otterizecloud/mocks" + "github.com/otterize/intents-operator/src/shared/testbase" + "github.com/samber/lo" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "testing" +) + +const ( + testNamespace string = "test-namespace" + intentsObjectName string = "test-client-intents" + clientName string = "test-client" + integrationName string = "test-integration" + tableName string = "test-table" + dbName string = "testdb" +) + +type DatabaseReconcilerTestSuite struct { + testbase.MocksSuiteBase + Reconciler *DatabaseReconciler + client *mocks.MockClient + mockCloudClient *otterizecloudmocks.MockCloudClient + namespacedName types.NamespacedName +} + +func (s *DatabaseReconcilerTestSuite) SetupTest() { + s.Controller = gomock.NewController(s.T()) + s.client = mocks.NewMockClient(s.Controller) + s.mockCloudClient = otterizecloudmocks.NewMockCloudClient(s.Controller) + + s.Reconciler = NewDatabaseReconciler( + s.client, + &runtime.Scheme{}, + s.mockCloudClient, + ) + + s.Recorder = record.NewFakeRecorder(100) + s.Reconciler.Recorder = s.Recorder + + s.namespacedName = types.NamespacedName{ + Namespace: testNamespace, + Name: intentsObjectName, + } +} + +func (s *DatabaseReconcilerTestSuite) TestSimpleDatabase() { + clientIntents := otterizev1alpha3.ClientIntents{ + ObjectMeta: metav1.ObjectMeta{ + Name: intentsObjectName, + Namespace: testNamespace, + }, + + Spec: &otterizev1alpha3.IntentsSpec{ + Service: otterizev1alpha3.Service{ + Name: clientName, + }, + Calls: []otterizev1alpha3.Intent{ + { + Name: integrationName, + Type: otterizev1alpha3.IntentTypeDatabase, + DatabaseResources: []otterizev1alpha3.DatabaseResource{{ + DatabaseName: dbName, + Table: tableName, + Operations: []otterizev1alpha3.DatabaseOperation{ + otterizev1alpha3.DatabaseOperationSelect, + otterizev1alpha3.DatabaseOperationInsert, + }, + }}, + }, + }, + }, + } + + expectedIntents := []graphqlclient.IntentInput{{ + ClientName: lo.ToPtr(clientName), + ServerName: lo.ToPtr(integrationName), + Namespace: lo.ToPtr(testNamespace), + ServerNamespace: lo.ToPtr(testNamespace), + Type: lo.ToPtr(graphqlclient.IntentTypeDatabase), + DatabaseResources: []*graphqlclient.DatabaseConfigInput{{ + Table: lo.ToPtr(tableName), + Dbname: lo.ToPtr(dbName), + Operations: []*graphqlclient.DatabaseOperation{ + lo.ToPtr(graphqlclient.DatabaseOperationSelect), + lo.ToPtr(graphqlclient.DatabaseOperationInsert), + }, + }}, + }} + + s.assertAppliedDatabaseIntents(clientIntents, expectedIntents) + s.ExpectEvent(ReasonAppliedDatabaseIntents) +} + +func (s *DatabaseReconcilerTestSuite) TestDontReportIntentsWithoutDatabaseType() { + clientIntents := otterizev1alpha3.ClientIntents{ + ObjectMeta: metav1.ObjectMeta{ + Name: intentsObjectName, + Namespace: testNamespace, + }, + + Spec: &otterizev1alpha3.IntentsSpec{ + Service: otterizev1alpha3.Service{ + Name: clientName, + }, + Calls: []otterizev1alpha3.Intent{ + { + Name: "server", + }, + }, + }, + } + + emptyIntents := otterizev1alpha3.ClientIntents{} + + s.client.EXPECT().Get(gomock.Any(), gomock.Eq(s.namespacedName), gomock.Eq(&emptyIntents)).DoAndReturn( + func(ctx context.Context, name types.NamespacedName, intents *otterizev1alpha3.ClientIntents, options ...client.ListOption) error { + clientIntents.DeepCopyInto(intents) + return nil + }) + + req := ctrl.Request{NamespacedName: s.namespacedName} + + res, err := s.Reconciler.Reconcile(context.Background(), req) + s.Require().NoError(err) + s.Require().Equal(ctrl.Result{}, res) +} + +func (s *DatabaseReconcilerTestSuite) TestNoSpecs() { + clientIntents := otterizev1alpha3.ClientIntents{ + ObjectMeta: metav1.ObjectMeta{ + Name: intentsObjectName, + Namespace: testNamespace, + }, + } + + s.expectHandleReconcilationErrorGracefully(clientIntents) +} + +func (s *DatabaseReconcilerTestSuite) expectHandleReconcilationErrorGracefully(clientIntents otterizev1alpha3.ClientIntents) { + emptyIntents := otterizev1alpha3.ClientIntents{} + + s.client.EXPECT().Get(gomock.Any(), gomock.Eq(s.namespacedName), gomock.Eq(&emptyIntents)).DoAndReturn( + func(ctx context.Context, name types.NamespacedName, intents *otterizev1alpha3.ClientIntents, options ...client.ListOption) error { + clientIntents.DeepCopyInto(intents) + return nil + }) + + req := ctrl.Request{NamespacedName: s.namespacedName} + res, err := s.Reconciler.Reconcile(context.Background(), req) + s.Require().NoError(err) + s.Require().Equal(ctrl.Result{}, res) +} + +func (s *DatabaseReconcilerTestSuite) assertAppliedDatabaseIntents(clientIntents otterizev1alpha3.ClientIntents, expectedIntents []graphqlclient.IntentInput) { + emptyIntents := otterizev1alpha3.ClientIntents{} + + s.client.EXPECT().Get(gomock.Any(), gomock.Eq(s.namespacedName), gomock.Eq(&emptyIntents)).DoAndReturn( + func(ctx context.Context, name types.NamespacedName, intents *otterizev1alpha3.ClientIntents, options ...client.ListOption) error { + clientIntents.DeepCopyInto(intents) + return nil + }) + + req := ctrl.Request{NamespacedName: s.namespacedName} + + s.mockCloudClient.EXPECT().ApplyDatabaseIntent(gomock.Any(), expectedIntents, graphqlclient.DBPermissionChangeApply).Return(nil).Times(1) + + res, err := s.Reconciler.Reconcile(context.Background(), req) + s.Require().NoError(err) + s.Require().Equal(ctrl.Result{}, res) +} + +func TestDatabaseReconcilerTestSuite(t *testing.T) { + suite.Run(t, new(DatabaseReconcilerTestSuite)) +} diff --git a/src/operator/copy-manifests-to-helm.sh b/src/operator/copy-manifests-to-helm.sh index 63b42ad15..77affb3a5 100755 --- a/src/operator/copy-manifests-to-helm.sh +++ b/src/operator/copy-manifests-to-helm.sh @@ -32,17 +32,14 @@ target_path=$(echo $CRD_DIR"/"$target_file); cp ./config/crd/k8s.otterize.com_kafkaserverconfigs.patched $target_path cp ./config/crd/k8s.otterize.com_kafkaserverconfigs.patched ./otterizecrds/kafkaserverconfigs-customresourcedefinition.yaml + + src_name=$(echo k8s.otterize.com_protectedservices.yaml | sed -e "s/^$src_prefix//" -e "s/$src_suffix//"); target_file=$(echo $src_name""$target_suffix); target_path=$(echo $CRD_DIR"/"$target_file); cp ./config/crd/k8s.otterize.com_protectedservices.patched $target_path cp ./config/crd/k8s.otterize.com_protectedservices.patched ./otterizecrds/protectedservices-customresourcedefinition.yaml -src_name=$(echo k8s.otterize.com_postgresqlserverconfigs.yaml | sed -e "s/^$src_prefix//" -e "s/$src_suffix//"); -target_file=$(echo $src_name""$target_suffix); -target_path=$(echo $CRD_DIR"/"$target_file); -cp ./config/crd/k8s.otterize.com_postgresqlserverconfigs.patched $target_path -cp ./config/crd/k8s.otterize.com_postgresqlserverconfigs.patched ./otterizecrds/postgresqlserverconfigs-customresourcedefinition.yaml # copy webhook and cluster role diff --git a/src/operator/main.go b/src/operator/main.go index 2b3c1e0a3..3cb4bc628 100644 --- a/src/operator/main.go +++ b/src/operator/main.go @@ -18,6 +18,7 @@ package main import ( "context" + "fmt" "github.com/amit7itz/goset" "github.com/bombsimon/logrusr/v3" "github.com/google/uuid" @@ -41,7 +42,6 @@ import ( "github.com/otterize/intents-operator/src/operator/webhooks" "github.com/otterize/intents-operator/src/shared/awsagent" "github.com/otterize/intents-operator/src/shared/azureagent" - "github.com/otterize/intents-operator/src/shared/clusterutils" "github.com/otterize/intents-operator/src/shared/filters" "github.com/otterize/intents-operator/src/shared/gcpagent" "github.com/otterize/intents-operator/src/shared/operator_cloud_client" @@ -56,20 +56,15 @@ import ( "github.com/otterize/intents-operator/src/shared/telemetries/telemetriesgql" "github.com/otterize/intents-operator/src/shared/telemetries/telemetrysender" "github.com/otterize/intents-operator/src/shared/version" - "github.com/samber/lo" "github.com/sirupsen/logrus" "github.com/spf13/viper" - v1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/metadata" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" "time" @@ -195,8 +190,23 @@ func main() { } signalHandlerCtx := ctrl.SetupSignalHandler() - clusterUID := setClusterUID(signalHandlerCtx, mgr, podNamespace) - componentinfo.SetGlobalContextId(telemetrysender.Anonymize(clusterUID)) + metadataClient, err := metadata.NewForConfig(ctrl.GetConfigOrDie()) + if err != nil { + logrus.WithError(err).Panic("unable to create metadata client") + } + mapping, err := mgr.GetRESTMapper().RESTMapping(schema.GroupKind{Group: "", Kind: "Namespace"}, "v1") + if err != nil { + logrus.WithError(err).Panic("unable to create Kubernetes API REST mapping") + } + kubeSystemUID := "" + kubeSystemNs, err := metadataClient.Resource(mapping.Resource).Get(signalHandlerCtx, "kube-system", metav1.GetOptions{}) + if err != nil || kubeSystemNs == nil { + logrus.Warningf("failed getting kubesystem UID: %s", err) + kubeSystemUID = fmt.Sprintf("rand-%s", uuid.New().String()) + } else { + kubeSystemUID = string(kubeSystemNs.UID) + } + componentinfo.SetGlobalContextId(telemetrysender.Anonymize(kubeSystemUID)) componentinfo.SetGlobalVersion(version.Version()) directClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: mgr.GetScheme()}) @@ -225,7 +235,6 @@ func main() { epNetpolReconciler.AddEgressRuleBuilder(svcEgressNetworkPolicyHandler) } - epIntentsReconciler := intents_reconcilers.NewServiceEffectiveIntentsReconciler(mgr.GetClient(), scheme, epGroupReconciler) additionalIntentsReconcilers = append(additionalIntentsReconcilers, epIntentsReconciler) @@ -299,6 +308,7 @@ func main() { if connectedToCloud { uploadConfiguration(signalHandlerCtx, otterizeCloudClient, enforcementConfig) operator_cloud_client.StartPeriodicallyReportConnectionToCloud(otterizeCloudClient, signalHandlerCtx) + netpolUploader := external_traffic.NewNetworkPolicyUploaderReconciler(mgr.GetClient(), mgr.GetScheme(), otterizeCloudClient) if err = netpolUploader.SetupWithManager(mgr); err != nil { logrus.WithError(err).Panic("unable to initialize NetworkPolicy reconciler") @@ -382,10 +392,6 @@ func main() { logrus.WithError(err).Panic("unable to create webhook v1alpha3", "webhook", "KafkaServerConfig") } - pgServerConfValidator := webhooks.NewPostgresConfValidator(mgr.GetClient()) - if err = (&otterizev1alpha3.PostgreSQLServerConfig{}).SetupWebhookWithManager(mgr, pgServerConfValidator); err != nil { - logrus.WithError(err).Panic("unable to create webhook v1alpha3", "webhook", "PostgreSQLServerConfig") - } } intentsReconciler := controllers.NewIntentsReconciler( @@ -530,41 +536,3 @@ func uploadConfiguration(ctx context.Context, otterizeCloudClient operator_cloud logrus.WithError(err).Error("Failed to report configuration to the cloud") } } - -func setClusterUID(ctx context.Context, mgr manager.Manager, podNamespace string) string { - config := ctrl.GetConfigOrDie() - metadataClient, err := metadata.NewForConfig(config) - if err != nil { - logrus.WithError(err).Panic("unable to create metadata client") - } - mapping, err := mgr.GetRESTMapper().RESTMapping(schema.GroupKind{Group: "", Kind: "Namespace"}, "v1") - if err != nil { - logrus.WithError(err).Panic("unable to create Kubernetes API REST mapping") - } - clusterUID := "" - kubeSystemNs, err := metadataClient.Resource(mapping.Resource).Get(ctx, "kube-system", metav1.GetOptions{}) - if err != nil || kubeSystemNs == nil { - logrus.Warningf("failed getting kubesystem UID: %s", err) - clusterUID = uuid.New().String() - } else { - clusterUID = string(kubeSystemNs.UID) - } - k8sclient, err := kubernetes.NewForConfig(config) - if err != nil { - logrus.WithError(err).Panic("unable to create client") - } - _, err = k8sclient.CoreV1().ConfigMaps(podNamespace).Create(ctx, &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterutils.OtterizeClusterUIDResourceName, - Namespace: podNamespace, - }, - Immutable: lo.ToPtr(true), - Data: map[string]string{clusterutils.OtterizeClusterUIDKeyName: clusterUID}, - }, metav1.CreateOptions{}) - - if err != nil && !k8serrors.IsAlreadyExists(err) { - logrus.WithError(err).Panic("unable to create config map with cluster UID") - } - - return clusterUID -} diff --git a/src/operator/otterizecrds/ensure.go b/src/operator/otterizecrds/ensure.go index aaa81d838..1ddddef31 100644 --- a/src/operator/otterizecrds/ensure.go +++ b/src/operator/otterizecrds/ensure.go @@ -20,9 +20,6 @@ var protectedServiceCRDContents []byte //go:embed kafkaserverconfigs-customresourcedefinition.yaml var KafkaServerConfigContents []byte -//go:embed postgresqlserverconfigs-customresourcedefinition.yaml -var PostgreSQLServerConfigContents []byte - func Ensure(ctx context.Context, k8sClient client.Client, operatorNamespace string) error { err := ensureCRD(ctx, k8sClient, operatorNamespace, clientIntentsCRDContents) if err != nil { @@ -36,10 +33,6 @@ func Ensure(ctx context.Context, k8sClient client.Client, operatorNamespace stri if err != nil { return errors.Errorf("failed to ensure KafkaServerConfig CRD: %w", err) } - err = ensureCRD(ctx, k8sClient, operatorNamespace, PostgreSQLServerConfigContents) - if err != nil { - return errors.Errorf("failed to ensure PostgreSQLServerConfig CRD: %w", err) - } return nil } @@ -96,8 +89,6 @@ func GetCRDDefinitionByName(name string) (*apiextensionsv1.CustomResourceDefinit err = yaml.Unmarshal(protectedServiceCRDContents, &crd) case "kafkaserverconfigs.k8s.otterize.com": err = yaml.Unmarshal(KafkaServerConfigContents, &crd) - case "postgresqlserverconfigs.k8s.otterize.com": - err = yaml.Unmarshal(PostgreSQLServerConfigContents, &crd) default: return nil, errors.Errorf("unknown CRD name: %s", name) } diff --git a/src/operator/otterizecrds/postgresqlserverconfigs-customresourcedefinition.yaml b/src/operator/otterizecrds/postgresqlserverconfigs-customresourcedefinition.yaml deleted file mode 100644 index c5a0c4cbe..000000000 --- a/src/operator/otterizecrds/postgresqlserverconfigs-customresourcedefinition.yaml +++ /dev/null @@ -1,82 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.14.0 - helm.sh/resource-policy: keep - creationTimestamp: null - labels: - app.kubernetes.io/part-of: otterize - name: postgresqlserverconfigs.k8s.otterize.com -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: intents-operator-webhook-service - namespace: otterize-system - path: /convert - conversionReviewVersions: - - v1 - group: k8s.otterize.com - names: - kind: PostgreSQLServerConfig - listKind: PostgreSQLServerConfigList - plural: postgresqlserverconfigs - singular: postgresqlserverconfig - scope: Namespaced - versions: - - name: v1alpha3 - schema: - openAPIV3Schema: - description: PostgreSQLServerConfig is the Schema for the databaseserverconfig API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: PostgreSQLServerConfigSpec defines the desired state of PostgreSQLServerConfig - properties: - address: - type: string - credentials: - properties: - password: - type: string - username: - type: string - required: - - password - - username - type: object - databaseName: - type: string - required: - - address - - credentials - - databaseName - type: object - status: - description: PostgreSQLServerConfigStatus defines the observed state of PostgreSQLServerConfig - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/src/operator/webhooks/postgresqlserverconfigs_webhook.go b/src/operator/webhooks/postgresqlserverconfigs_webhook.go deleted file mode 100644 index 938b4badc..000000000 --- a/src/operator/webhooks/postgresqlserverconfigs_webhook.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package webhooks - -import ( - "context" - "fmt" - otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" - "github.com/otterize/intents-operator/src/shared/errors" - "github.com/samber/lo" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -type PostgresConfValidator struct { - client.Client -} - -func (v *PostgresConfValidator) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(&otterizev1alpha3.PostgreSQLServerConfig{}). - WithValidator(v). - Complete() -} - -func NewPostgresConfValidator(c client.Client) *PostgresConfValidator { - return &PostgresConfValidator{ - Client: c, - } -} - -//+kubebuilder:webhook:path=/validate-k8s-otterize-com-v1alpha3-postgresqlserverconfig,mutating=false,failurePolicy=fail,sideEffects=None,groups=k8s.otterize.com,resources=postgresqlserverconfig,verbs=create;update,versions=v1alpha3,name=postgresqlserverconfig.kb.io,admissionReviewVersions=v1 - -var _ webhook.CustomValidator = &PostgresConfValidator{} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (v *PostgresConfValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - return nil, nil -} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (v *PostgresConfValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - // We validate no other PGServerConf with the same name exists in the cluster - // It's a little hacky since it is a namespace scoped resource but we enforce name uniqueness across the cluster - allErrs := field.ErrorList{} - pgServerConf := obj.(*otterizev1alpha3.PostgreSQLServerConfig) - pgServerConfList := otterizev1alpha3.PostgreSQLServerConfigList{} - if err := v.List(ctx, &pgServerConfList); err != nil { - return nil, errors.Wrap(err) - } - - if err := v.ValidateNoDuplicateConfNames(pgServerConf.Name, pgServerConfList.Items); err != nil { - allErrs = append(allErrs, err) - gvk := pgServerConf.GroupVersionKind() - return nil, k8serrors.NewInvalid( - schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, - pgServerConf.Name, allErrs) - } - - return nil, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (v *PostgresConfValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - allErrs := field.ErrorList{} - pgServerConf := newObj.(*otterizev1alpha3.PostgreSQLServerConfig) - pgServerConfList := otterizev1alpha3.PostgreSQLServerConfigList{} - if err := v.List(ctx, &pgServerConfList); err != nil { - return nil, errors.Wrap(err) - } - - if err := v.ValidateNoDuplicateConfNames(pgServerConf.Name, pgServerConfList.Items); err != nil { - allErrs = append(allErrs, err) - gvk := pgServerConf.GroupVersionKind() - return nil, k8serrors.NewInvalid( - schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, - pgServerConf.Name, allErrs) - } - - return nil, nil -} - -func (v *PostgresConfValidator) ValidateNoDuplicateConfNames( - currName string, - confList []otterizev1alpha3.PostgreSQLServerConfig) *field.Error { - - conf, found := lo.Find(confList, func(conf otterizev1alpha3.PostgreSQLServerConfig) bool { - return currName == conf.Name - }) - - if found { - return &field.Error{ - Type: field.ErrorTypeDuplicate, - Field: "name", - BadValue: currName, - Detail: fmt.Sprintf( - "Postgres server config already exists with name %s. Existing resource: %s.%s", - currName, conf.Name, conf.Namespace), - } - } - - return nil -} diff --git a/src/shared/clusterutils/clusterid.go b/src/shared/clusterutils/clusterid.go deleted file mode 100644 index b3c8196d9..000000000 --- a/src/shared/clusterutils/clusterid.go +++ /dev/null @@ -1,34 +0,0 @@ -package clusterutils - -import ( - "context" - "fmt" - "github.com/otterize/intents-operator/src/shared/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "os" - ctrl "sigs.k8s.io/controller-runtime" -) - -const OtterizeClusterUIDResourceName = "otterize-cluster-uid" -const OtterizeClusterUIDKeyName = "clusteruid" - -// GetClusterUID Fetches cluster ID from the config map created in the Otterize namespace -func GetClusterUID(ctx context.Context) (string, error) { - k8sclient, err := kubernetes.NewForConfig(ctrl.GetConfigOrDie()) - if err != nil { - return "", errors.Wrap(err) - } - podNamespace := os.Getenv("POD_NAMESPACE") - configMap, err := k8sclient.CoreV1().ConfigMaps(podNamespace).Get(ctx, OtterizeClusterUIDResourceName, metav1.GetOptions{}) - if err != nil { - return "", errors.Wrap(err) - } - - clusterUID, ok := configMap.Data[OtterizeClusterUIDKeyName] - if !ok || clusterUID == "" { - return "", errors.Wrap(fmt.Errorf("invalid cluster UID found in %s config map", OtterizeClusterUIDResourceName)) - } - - return clusterUID, nil -} diff --git a/src/shared/clusterutils/username.go b/src/shared/clusterutils/username.go deleted file mode 100644 index df1e7c93c..000000000 --- a/src/shared/clusterutils/username.go +++ /dev/null @@ -1,47 +0,0 @@ -package clusterutils - -import ( - "crypto/md5" - "encoding/hex" - "fmt" - "strings" -) - -const ( - HashedUsernameSectionMaxLength = 17 -) - -func BuildHashedUsername(workloadName, namespace, clusterUID string) string { - // We're trying to achieve uniqueness while not completely trimming the clusterUID if username + namespace are long - // so we trim each section separately leaving room for 6 char hash suffix and string formatting chars - fullUsername := fmt.Sprintf("%s-%s-%s", workloadName, namespace, clusterUID) - hash := md5.Sum([]byte(fullUsername)) - - if len(workloadName) > HashedUsernameSectionMaxLength { - workloadName = workloadName[:HashedUsernameSectionMaxLength] - } - - if len(namespace) > HashedUsernameSectionMaxLength { - namespace = namespace[:HashedUsernameSectionMaxLength] - } - - if len(clusterUID) > HashedUsernameSectionMaxLength { - clusterUID = clusterUID[:HashedUsernameSectionMaxLength] - } - - clusterUID = strings.TrimSuffix(clusterUID, "-") // Just in case we trimmed at a hyphen separator - - hashSuffix := hex.EncodeToString(hash[:])[:6] - return fmt.Sprintf("%s-%s-id-%s-%s", workloadName, namespace, clusterUID, hashSuffix) -} - -// KubernetesToPostgresName translates a name with Kubernetes conventions to Postgres conventions -func KubernetesToPostgresName(kubernetesName string) string { - // '.' are replaced with dunders '__' - // '-' are replaced with single underscores '_' - return strings.ReplaceAll(strings.ReplaceAll(kubernetesName, ".", "__"), "-", "_") -} - -func PostgresToKubernetesName(pgName string) string { - return strings.ReplaceAll(strings.ReplaceAll(pgName, "__", "."), "_", "-") -} diff --git a/src/shared/databaseconfigurator/postgres/postgres.go b/src/shared/databaseconfigurator/postgres/postgres.go deleted file mode 100644 index 64c07940b..000000000 --- a/src/shared/databaseconfigurator/postgres/postgres.go +++ /dev/null @@ -1,477 +0,0 @@ -package postgres - -import ( - "context" - "fmt" - "github.com/jackc/pgx/v5" - otterizev1alpha3 "github.com/otterize/intents-operator/src/operator/api/v1alpha3" - "github.com/otterize/intents-operator/src/shared/clusterutils" - "github.com/otterize/intents-operator/src/shared/errors" - "github.com/samber/lo" - "github.com/sirupsen/logrus" - "golang.org/x/exp/slices" - "net/url" - "strings" - "sync" -) - -const ( - PGRevokeAllTableStatement SQLSprintfStatement = "REVOKE ALL ON TABLE %s FROM %s" - PGRevokeAllOnSeqStatement SQLSprintfStatement = "REVOKE ALL ON SEQUENCE %s FROM %s" - PGGrantStatement SQLSprintfStatement = "GRANT %s ON %s to %s" - PGGrantOnAllTablesInSchemaStatement SQLSprintfStatement = "GRANT %s ON ALL TABLES IN SCHEMA %s to %s" - PGGrantOnAllSequencesInSchemaStatement SQLSprintfStatement = "GRANT %s ON ALL SEQUENCES IN SCHEMA %s to %s" - PGRevokeOnAllTablesInSchemaStatement SQLSprintfStatement = "REVOKE ALL ON ALL TABLES IN SCHEMA %s FROM %s" - PGRevokeOnAllSequencesInSchemaStatement SQLSprintfStatement = "REVOKE ALL ON ALL SEQUENCES IN SCHEMA %s FROM %s" - PGSelectUserQuery = "SELECT FROM pg_catalog.pg_user where usename = $1" - PGSelectPrivilegesQuery = "SELECT table_schema, table_name FROM information_schema.table_privileges where grantee = $1" - PGSSelectTableSequencesPrivilegesQuery = "SELECT split_part(column_default, '''', 2) FROM information_schema.columns WHERE column_default LIKE 'nextval%' and table_schema=$1 and table_name=$2" - PGSSelectSchemaNamesQuery = "SELECT schema_name From information_schema.schemata where schema_name!='pg_catalog' and schema_name!='pg_toast' and schema_name!='information_schema'" - PGSelectAllDatabasesWithConnectPermissions = "SELECT datname from pg_catalog.pg_database d where datallowconn and datistemplate=false and has_database_privilege(d.datname, 'CONNECT');" - PGDefaultDatabase = "postgres" -) - -type SQLSprintfStatement string -type NonUserInputString string - -func tableNameToIdentifier(tableName string) pgx.Identifier { - return strings.Split(tableName, ".") -} - -type PostgresTableIdentifier struct { - tableSchema string - tableName string -} - -func (p PostgresTableIdentifier) ToPGXIdentifier() pgx.Identifier { - return pgx.Identifier{p.tableSchema, p.tableName} -} - -func (s SQLSprintfStatement) PrepareSanitized(a ...any) (string, error) { - sanitizedItems := make([]any, len(a)) - for i, formatInput := range a { - if dbOperations, ok := formatInput.([]otterizev1alpha3.DatabaseOperation); ok { - asStrings := lo.Map(dbOperations, func(op otterizev1alpha3.DatabaseOperation, _ int) string { return string(op) }) - sanitizedItems[i] = strings.Join(asStrings, ",") - continue - } - - // String that was marked explicitly as non-user doesn't have to be sanitized - if inputStr, ok := formatInput.(NonUserInputString); ok { - sanitizedItems[i] = fmt.Sprintf("'%s'", inputStr) - continue - } - - // Postgres identifiers like table names, users, column names, etc.. - if ident, ok := formatInput.(pgx.Identifier); ok { - sanitizedItems[i] = ident.Sanitize() - continue - } - - return "", errors.Errorf("received sanitize input '%v' with type '%T', which was not string or DatabaseOperation", formatInput, formatInput) - } - return fmt.Sprintf(string(s), sanitizedItems...), nil -} - -type PostgresConfigurator struct { - conn *pgx.Conn - databaseInfo otterizev1alpha3.PostgreSQLServerConfigSpec - setConnMutex sync.Mutex -} - -func NewPostgresConfigurator(pgServerConfSpec otterizev1alpha3.PostgreSQLServerConfigSpec) *PostgresConfigurator { - return &PostgresConfigurator{ - databaseInfo: pgServerConfSpec, - setConnMutex: sync.Mutex{}, - } -} - -func (p *PostgresConfigurator) ConfigureDBFromIntents( - ctx context.Context, - workloadName string, - namespace string, - intents []otterizev1alpha3.Intent, - permissionChange otterizev1alpha3.DBPermissionChange) error { - - if err := p.SetConnection(ctx, PGDefaultDatabase); err != nil { - return errors.Wrap(err) - } - - dbnameToDatabaseResources, err := p.ExtractDBNameToDatabaseResourcesFromIntents(ctx, intents) - if err != nil { - return errors.Wrap(err) - } - clusterID, err := clusterutils.GetClusterUID(ctx) - if err != nil { - return err - } - username := clusterutils.BuildHashedUsername(workloadName, namespace, clusterID) - pgUsername := clusterutils.KubernetesToPostgresName(username) - exists, err := p.ValidateUserExists(ctx, pgUsername) - if err != nil { - return errors.Wrap(err) - } - - if !exists { - logrus.WithField("username", pgUsername).Info( - "Waiting for Postgres user to be created before configuring permissions") - return errors.Wrap(fmt.Errorf( - "user for workload %s.%s doesn't exist in DB yet, cannot configure permissions", workloadName, namespace)) - } - - for dbname, dbResources := range dbnameToDatabaseResources { - if err := p.SetConnection(ctx, dbname); err != nil { - return errors.Wrap(err) - } - - if permissionChange != otterizev1alpha3.DBPermissionChangeDelete { - // Need to check whether tables were deleted from intents, and revoke permissions for them - allowedTablesDiff, err := p.getAllowedTablesDiffForUser(ctx, pgUsername, dbResources) - if err != nil { - return errors.Wrap(err) - } - - if err = p.revokeRemovedTablesPermissions(ctx, allowedTablesDiff, pgUsername); err != nil { - return errors.Wrap(err) - } - } - - statementsBatch, err := p.SQLBatchFromDBResources(ctx, pgUsername, dbResources, permissionChange) - if err != nil { - return errors.Wrap(err) - } - err = p.SendBatch(ctx, &statementsBatch) - if err != nil { - return errors.Wrap(err) - } - } - - return nil - -} - -func (p *PostgresConfigurator) SendBatch(ctx context.Context, statementsBatch *pgx.Batch) error { - batchResults := p.conn.SendBatch(ctx, statementsBatch) - - for i := 0; i < statementsBatch.Len(); i++ { - if _, err := batchResults.Exec(); err != nil { - return TranslatePostgresCommandsError(err) - } - } - if err := batchResults.Close(); err != nil { - // Intentionally no error returned - clean up error - logrus.WithError(err).Errorf("Failed closing batch results") - } - return nil -} - -func (p *PostgresConfigurator) SetConnection(ctx context.Context, databaseName string) error { - connectionString := p.FormatConnectionString(databaseName) - conn, err := pgx.Connect(ctx, connectionString) - if err != nil { - pgErr, ok := TranslatePostgresConnectionError(err) - if ok { - return errors.Wrap(fmt.Errorf(pgErr)) - } - return errors.Wrap(err) - } - p.setConnMutex.Lock() - defer p.setConnMutex.Unlock() - if p.conn == nil { - p.conn = conn - return nil - } - if err := p.conn.Close(ctx); err != nil { - // Intentionally no error returned - clean up error - logrus.Errorf("Failed closing connection to: %s", p.databaseInfo.Address) - } - p.conn = conn - return nil -} - -func (p *PostgresConfigurator) ExtractDBNameToDatabaseResourcesFromIntents(ctx context.Context, intents []otterizev1alpha3.Intent) (map[string][]otterizev1alpha3.DatabaseResource, error) { - scopeToDatabaseResources := make(map[string][]otterizev1alpha3.DatabaseResource) - for _, intent := range intents { - for _, dbResource := range intent.DatabaseResources { - if _, ok := scopeToDatabaseResources[dbResource.DatabaseName]; !ok { - scopeToDatabaseResources[dbResource.DatabaseName] = []otterizev1alpha3.DatabaseResource{dbResource} - continue - } - // TODO: Smart merge instead of just adding - resources := scopeToDatabaseResources[dbResource.DatabaseName] - scopeToDatabaseResources[dbResource.DatabaseName] = append(resources, dbResource) - } - } - rows, err := p.conn.Query(ctx, PGSelectAllDatabasesWithConnectPermissions) - if err != nil { - return nil, errors.Wrap(err) - } - defer rows.Close() - for rows.Next() { - var databaseName string - if err := rows.Scan(&databaseName); err != nil { - return nil, errors.Wrap(err) - } - if _, exists := scopeToDatabaseResources[databaseName]; exists { - continue - } - scopeToDatabaseResources[databaseName] = []otterizev1alpha3.DatabaseResource{} - } - return scopeToDatabaseResources, nil -} - -func (p *PostgresConfigurator) SQLBatchFromDBResources(ctx context.Context, username string, dbResources []otterizev1alpha3.DatabaseResource, change otterizev1alpha3.DBPermissionChange) (pgx.Batch, error) { - - batch := pgx.Batch{} - - if change == otterizev1alpha3.DBPermissionChangeDelete { - // Intent was deleted, revoke all client permissions from mentioned tables - for _, resource := range dbResources { - if resource.Table == "" { - err := p.QueueRevokePermissionsByDatabaseNameStatements(ctx, &batch, username) - if err != nil { - return pgx.Batch{}, errors.Wrap(err) - } - continue - } - err := p.queueRevokeAllOnTableAndSequencesStatements(ctx, &batch, databaseConfigInputToPostgresTableIdentifier(resource), username) - if err != nil { - return pgx.Batch{}, errors.Wrap(err) - } - } - return batch, nil - } - - // Intent was created or updated, so we revoke current permissions and grant new ones - for _, resource := range dbResources { - if resource.Table == "" { - err := p.queueAddPermissionsByDatabaseNameStatements(ctx, &batch, resource, username) - if err != nil { - return pgx.Batch{}, errors.Wrap(err) - } - continue - } - err := p.queueAddPermissionsToTableStatements(ctx, &batch, resource, username) - if err != nil { - return pgx.Batch{}, errors.Wrap(err) - } - } - return batch, nil -} - -func (p *PostgresConfigurator) queueAddPermissionsToTableStatements(ctx context.Context, batch *pgx.Batch, resource otterizev1alpha3.DatabaseResource, username string) error { - postgresTableIdentifier := databaseConfigInputToPostgresTableIdentifier(resource) - rows, err := p.conn.Query(ctx, PGSSelectTableSequencesPrivilegesQuery, postgresTableIdentifier.tableSchema, postgresTableIdentifier.tableName) - if err != nil { - return errors.Wrap(err) - } - defer rows.Close() - - for rows.Next() { - var sequenceName string - if err := rows.Scan(&sequenceName); err != nil { - return errors.Wrap(err) - } - stmt, err := PGGrantStatement.PrepareSanitized([]otterizev1alpha3.DatabaseOperation{otterizev1alpha3.DatabaseOperationAll}, pgx.Identifier{postgresTableIdentifier.tableSchema, sequenceName}, pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - } - // We always include the "revoke all" statement to make sure deleted permissions are removed - stmt, err := PGRevokeAllTableStatement.PrepareSanitized(tableNameToIdentifier(resource.Table), pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - operations := p.getGrantOperations(resource.Operations) - stmt, err = PGGrantStatement.PrepareSanitized(operations, tableNameToIdentifier(resource.Table), pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - return nil -} - -// CloseConnection closes the connection to the database and logs an error -// We cannot use defer on the close connection since it runs in a for loop, and can potentially keep a lot of -// connections open until closing them, which Postgres doesn't like that much -func (p *PostgresConfigurator) CloseConnection(ctx context.Context) { - if p.conn == nil { - return - } - if err := p.conn.Close(ctx); err != nil { - // Intentionally no error returned - clean up error - logrus.Errorf("Failed closing connection to: %s", p.databaseInfo.Address) - } -} - -func (p *PostgresConfigurator) getGrantOperations(operations []otterizev1alpha3.DatabaseOperation) []otterizev1alpha3.DatabaseOperation { - - if len(operations) == 0 || slices.Contains(operations, otterizev1alpha3.DatabaseOperationAll) { - // Omit everything else in case it's included to avoid Postgres errors, include just 'ALL' - return []otterizev1alpha3.DatabaseOperation{otterizev1alpha3.DatabaseOperationAll} - } - - return operations - -} - -// getAllowedTablesDiffForUser gets the diff between all current tables with permissions for 'username', -// and tables specified in the intent. If tables with current permissions do not exist in the intent all permissions -// from them should be removed, as they were deleted from the ClientIntents resource. -func (p *PostgresConfigurator) getAllowedTablesDiffForUser(ctx context.Context, username string, dbResources []otterizev1alpha3.DatabaseResource) ([]PostgresTableIdentifier, error) { - intentTables := lo.Map(dbResources, func(resource otterizev1alpha3.DatabaseResource, _ int) PostgresTableIdentifier { - return databaseConfigInputToPostgresTableIdentifier(resource) - }) - allowedTablesDiff := make([]PostgresTableIdentifier, 0) - rows, err := p.conn.Query(ctx, PGSelectPrivilegesQuery, username) - if err != nil { - return nil, errors.Wrap(err) - } - defer rows.Close() - for rows.Next() { - var allowedTable PostgresTableIdentifier - if err := rows.Scan(&allowedTable.tableSchema, &allowedTable.tableName); err != nil { - return nil, errors.Wrap(err) - } - logrus.Debugf("User %s has permissions for allowedTable: %s", username, allowedTable) - if !slices.Contains(intentTables, allowedTable) { - allowedTablesDiff = append(allowedTablesDiff, allowedTable) - } - } - - return allowedTablesDiff, nil -} - -func databaseConfigInputToPostgresTableIdentifier(resource otterizev1alpha3.DatabaseResource) PostgresTableIdentifier { - tableIdentifier := strings.Split(resource.Table, ".") - if len(tableIdentifier) == 2 { - return PostgresTableIdentifier{tableSchema: tableIdentifier[0], tableName: tableIdentifier[1]} - } - return PostgresTableIdentifier{tableSchema: "public", tableName: resource.Table} -} - -func (p *PostgresConfigurator) revokeRemovedTablesPermissions(ctx context.Context, allowedTablesDiff []PostgresTableIdentifier, username string) error { - batch := pgx.Batch{} - for _, table := range allowedTablesDiff { - err := p.queueRevokeAllOnTableAndSequencesStatements(ctx, &batch, table, username) - if err != nil { - return errors.Wrap(err) - } - } - batchResults := p.conn.SendBatch(ctx, &batch) - for i := 0; i < batch.Len(); i++ { - if _, err := batchResults.Exec(); err != nil { - return TranslatePostgresCommandsError(err) - } - } - if err := batchResults.Close(); err != nil { - logrus.WithError(err).Errorf("Failed closing batch results") - } - - return nil -} - -func (p *PostgresConfigurator) queueRevokeAllOnTableAndSequencesStatements(ctx context.Context, batch *pgx.Batch, table PostgresTableIdentifier, username string) error { - rows, err := p.conn.Query(ctx, PGSSelectTableSequencesPrivilegesQuery, table.tableSchema, table.tableName) - if err != nil { - return errors.Wrap(err) - } - defer rows.Close() - - for rows.Next() { - var sequenceName string - if err := rows.Scan(&sequenceName); err != nil { - return errors.Wrap(err) - } - stmt, err := PGRevokeAllOnSeqStatement.PrepareSanitized(pgx.Identifier{table.tableSchema, sequenceName}, pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - } - - stmt, err := PGRevokeAllTableStatement.PrepareSanitized(table.ToPGXIdentifier(), pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - return nil -} - -func (p *PostgresConfigurator) queueAddPermissionsByDatabaseNameStatements(ctx context.Context, batch *pgx.Batch, resource otterizev1alpha3.DatabaseResource, username string) error { - // Get all schemas in current database - rows, err := p.conn.Query(ctx, PGSSelectSchemaNamesQuery) - if err != nil { - return errors.Wrap(err) - } - defer rows.Close() - - // Grant privileges on all tables/sequences in every schema - for rows.Next() { - var schemaName string - if err := rows.Scan(&schemaName); err != nil { - return errors.Wrap(err) - } - stmt, err := PGGrantOnAllSequencesInSchemaStatement.PrepareSanitized([]otterizev1alpha3.DatabaseOperation{otterizev1alpha3.DatabaseOperationAll}, pgx.Identifier{schemaName}, pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - operations := p.getGrantOperations(resource.Operations) - stmt, err = PGGrantOnAllTablesInSchemaStatement.PrepareSanitized(operations, pgx.Identifier{schemaName}, pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - } - return nil -} - -func (p *PostgresConfigurator) QueueRevokePermissionsByDatabaseNameStatements(ctx context.Context, batch *pgx.Batch, username string) error { - // Get all schemas in current database - rows, err := p.conn.Query(ctx, PGSSelectSchemaNamesQuery) - if err != nil { - return errors.Wrap(err) - } - defer rows.Close() - - // Grant privileges on all tables/sequences in every schema - for rows.Next() { - var schemaName string - if err := rows.Scan(&schemaName); err != nil { - return errors.Wrap(err) - } - stmt, err := PGRevokeOnAllSequencesInSchemaStatement.PrepareSanitized(pgx.Identifier{schemaName}, pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - stmt, err = PGRevokeOnAllTablesInSchemaStatement.PrepareSanitized(pgx.Identifier{schemaName}, pgx.Identifier{username}) - if err != nil { - return errors.Wrap(err) - } - batch.Queue(stmt) - } - return nil -} - -func (p *PostgresConfigurator) FormatConnectionString(databaseName string) string { - return fmt.Sprintf( - "postgres://%s:%s@%s/%s", - p.databaseInfo.Credentials.Username, - url.QueryEscape(p.databaseInfo.Credentials.Password), - p.databaseInfo.Address, - databaseName) -} - -func (p *PostgresConfigurator) ValidateUserExists(ctx context.Context, user string) (bool, error) { - row, err := p.conn.Query(ctx, PGSelectUserQuery, user) - if err != nil { - return false, errors.Wrap(err) - } - defer row.Close() // "row" either holds 1 or 0 rows, and we must call Close() before reusing the connection again - - return row.Next(), nil -} diff --git a/src/shared/databaseconfigurator/postgres/utils.go b/src/shared/databaseconfigurator/postgres/utils.go deleted file mode 100644 index 99d23529c..000000000 --- a/src/shared/databaseconfigurator/postgres/utils.go +++ /dev/null @@ -1,54 +0,0 @@ -package postgres - -import ( - "context" - "fmt" - "github.com/jackc/pgx/v5/pgconn" - "github.com/otterize/intents-operator/src/shared/errors" - "net" -) - -func TranslatePostgresConnectionError(err error) (string, bool) { - if opErr := &(net.OpError{}); errors.As(err, &opErr) || errors.Is(err, context.DeadlineExceeded) { - return "Can't reach the server", true - } - - if connErr := &(pgconn.ConnectError{}); errors.As(err, &connErr) { - return "Can't reach the server", true - } - - if dnsErr := &(net.DNSError{}); errors.As(err, &dnsErr) { - return "Can't resolve hostname", true - } - - if pgErr := &(pgconn.PgError{}); errors.As(err, &pgErr) { - // See: https://www.postgresql.org/docs/current/errcodes-appendix.html - if pgErr.Code == "28P01" || pgErr.Code == "28000" { - return "Invalid credentials", true - } - if pgErr.Code == "3D000" { - return fmt.Sprintf("Database doesn't exist: %s", pgErr.Message), true - } - } - - return "", false -} - -func IsInvalidAuthorizationError(err error) bool { - if pgErr := &(pgconn.PgError{}); errors.As(err, &pgErr) { - if pgErr.Code == "28000" { - return true - } - } - return false -} - -func TranslatePostgresCommandsError(err error) error { - if pgErr := &(pgconn.PgError{}); errors.As(err, &pgErr) { - // See: https://www.postgresql.org/docs/current/errcodes-appendix.html - if pgErr.Code == "42P01" || pgErr.Code == "3F000" { - return errors.Wrap(fmt.Errorf("bad schema/table name: %s", pgErr.Message)) - } - } - return errors.Wrap(err) -} diff --git a/src/shared/local.env b/src/shared/local.env new file mode 100644 index 000000000..9b63f6fcf --- /dev/null +++ b/src/shared/local.env @@ -0,0 +1,20 @@ +# This file is used for local development and testing of the operator +# - Duplicate it to .env or use it as is to set the environment variables for main.go +# - In order to apply otterize CRDS to your cluster run "kubectl apply -k config/crd/" +# - This file is used for local development of multiple operators + +# Intents operator vars ========================= +OTTERIZE_CLIENT_ID= +OTTERIZE_CLIENT_SECRET= +OTTERIZE_API_ADDRESS=http://localhost:3000/api + +OTTERIZE_TELEMETRY_ENABLED=false + +OTTERIZE_POD_NAME=intents-operator +OTTERIZE_POD_NAMESPACE=otterize-system +OTTERIZE_SELF_SIGNED_CERT=false +OTTERIZE_TELEMETRY_ENABLED=false +OTTERIZE_DISABLE_WEBHOOK_SERVER=true + +# Credentials operator vars ===================== +POD_NAMESPACE=otterize-system diff --git a/src/shared/operator_cloud_client/cloud_api.go b/src/shared/operator_cloud_client/cloud_api.go index af758cadf..058552d26 100644 --- a/src/shared/operator_cloud_client/cloud_api.go +++ b/src/shared/operator_cloud_client/cloud_api.go @@ -3,7 +3,6 @@ package operator_cloud_client import ( "context" "github.com/Khan/genqlient/graphql" - "github.com/otterize/intents-operator/src/shared/clusterutils" "github.com/otterize/intents-operator/src/shared/errors" "github.com/otterize/intents-operator/src/shared/otterizecloud/graphqlclient" "github.com/otterize/intents-operator/src/shared/otterizecloud/otterizecloudclient" @@ -18,6 +17,7 @@ type CloudClient interface { ReportNetworkPolicies(ctx context.Context, namespace string, policies []graphqlclient.NetworkPolicyInput) error ReportExternallyAccessibleServices(ctx context.Context, namespace string, services []graphqlclient.ExternallyAccessibleServiceInput) error ReportProtectedServices(ctx context.Context, namespace string, protectedServices []graphqlclient.ProtectedServiceInput) error + ApplyDatabaseIntent(ctx context.Context, intents []graphqlclient.IntentInput, action graphqlclient.DBPermissionChange) error } type CloudClientImpl struct { @@ -51,12 +51,7 @@ func (c *CloudClientImpl) ReportAppliedIntents( namespace *string, intents []*graphqlclient.IntentInput) error { - clusterID, err := clusterutils.GetClusterUID(ctx) - if err != nil { - return errors.Wrap(err) - } - - _, err = graphqlclient.ReportAppliedKubernetesIntents(ctx, c.client, namespace, intents, &clusterID) + _, err := graphqlclient.ReportAppliedKubernetesIntents(ctx, c.client, namespace, intents) if err != nil { return errors.Wrap(err) } @@ -112,3 +107,10 @@ func (c *CloudClientImpl) ReportProtectedServices(ctx context.Context, namespace _, err := graphqlclient.ReportProtectedServicesSnapshot(ctx, c.client, namespace, protectedServices) return errors.Wrap(err) } + +func (c *CloudClientImpl) ApplyDatabaseIntent(ctx context.Context, intents []graphqlclient.IntentInput, action graphqlclient.DBPermissionChange) error { + if _, err := graphqlclient.HandleDatabaseIntents(ctx, c.client, intents, action); err != nil { + return errors.Wrap(err) + } + return nil +} diff --git a/src/shared/otterizecloud/graphqlclient/generated.go b/src/shared/otterizecloud/graphqlclient/generated.go index 2e2df6517..0c102df5e 100644 --- a/src/shared/otterizecloud/graphqlclient/generated.go +++ b/src/shared/otterizecloud/graphqlclient/generated.go @@ -37,6 +37,13 @@ const ( ComponentTypeNetworkMapper ComponentType = "NETWORK_MAPPER" ) +type DBPermissionChange string + +const ( + DBPermissionChangeApply DBPermissionChange = "APPLY" + DBPermissionChangeDelete DBPermissionChange = "DELETE" +) + type DatabaseConfigInput struct { Dbname *string `json:"dbname"` Table *string `json:"table"` @@ -108,6 +115,16 @@ const ( HTTPMethodAll HTTPMethod = "ALL" ) +// HandleDatabaseIntentsResponse is returned by HandleDatabaseIntents on success. +type HandleDatabaseIntentsResponse struct { + HandleDatabaseIntents bool `json:"handleDatabaseIntents"` +} + +// GetHandleDatabaseIntents returns HandleDatabaseIntentsResponse.HandleDatabaseIntents, and is useful for accessing the field via an interface. +func (v *HandleDatabaseIntentsResponse) GetHandleDatabaseIntents() bool { + return v.HandleDatabaseIntents +} + type IntentInput struct { Namespace *string `json:"namespace"` ClientName *string `json:"clientName"` @@ -485,11 +502,22 @@ const ( UserErrorTypeAppliedIntentsError UserErrorType = "APPLIED_INTENTS_ERROR" ) +// __HandleDatabaseIntentsInput is used internally by genqlient +type __HandleDatabaseIntentsInput struct { + Intents []IntentInput `json:"intents"` + Action DBPermissionChange `json:"action"` +} + +// GetIntents returns __HandleDatabaseIntentsInput.Intents, and is useful for accessing the field via an interface. +func (v *__HandleDatabaseIntentsInput) GetIntents() []IntentInput { return v.Intents } + +// GetAction returns __HandleDatabaseIntentsInput.Action, and is useful for accessing the field via an interface. +func (v *__HandleDatabaseIntentsInput) GetAction() DBPermissionChange { return v.Action } + // __ReportAppliedKubernetesIntentsInput is used internally by genqlient type __ReportAppliedKubernetesIntentsInput struct { Namespace *string `json:"namespace"` Intents []*IntentInput `json:"intents"` - ClusterId *string `json:"clusterId"` } // GetNamespace returns __ReportAppliedKubernetesIntentsInput.Namespace, and is useful for accessing the field via an interface. @@ -498,9 +526,6 @@ func (v *__ReportAppliedKubernetesIntentsInput) GetNamespace() *string { return // GetIntents returns __ReportAppliedKubernetesIntentsInput.Intents, and is useful for accessing the field via an interface. func (v *__ReportAppliedKubernetesIntentsInput) GetIntents() []*IntentInput { return v.Intents } -// GetClusterId returns __ReportAppliedKubernetesIntentsInput.ClusterId, and is useful for accessing the field via an interface. -func (v *__ReportAppliedKubernetesIntentsInput) GetClusterId() *string { return v.ClusterId } - // __ReportComponentStatusInput is used internally by genqlient type __ReportComponentStatusInput struct { Component ComponentType `json:"component"` @@ -581,250 +606,307 @@ type dummyResponse struct { // GetDummyError returns dummyResponse.DummyError, and is useful for accessing the field via an interface. func (v *dummyResponse) GetDummyError() UserErrorType { return v.DummyError } +// The query or mutation executed by HandleDatabaseIntents. +const HandleDatabaseIntents_Operation = ` +mutation HandleDatabaseIntents ($intents: [IntentInput!]!, $action: DBPermissionChange!) { + handleDatabaseIntents(intents: $intents, action: $action) +} +` + +func HandleDatabaseIntents( + ctx_ context.Context, + client_ graphql.Client, + intents []IntentInput, + action DBPermissionChange, +) (*HandleDatabaseIntentsResponse, error) { + req_ := &graphql.Request{ + OpName: "HandleDatabaseIntents", + Query: HandleDatabaseIntents_Operation, + Variables: &__HandleDatabaseIntentsInput{ + Intents: intents, + Action: action, + }, + } + var err_ error + + var data_ HandleDatabaseIntentsResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by ReportAppliedKubernetesIntents. +const ReportAppliedKubernetesIntents_Operation = ` +mutation ReportAppliedKubernetesIntents ($namespace: String!, $intents: [IntentInput!]!) { + reportAppliedKubernetesIntents(namespace: $namespace, intents: $intents) +} +` + func ReportAppliedKubernetesIntents( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, namespace *string, intents []*IntentInput, - clusterId *string, ) (*ReportAppliedKubernetesIntentsResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportAppliedKubernetesIntents", - Query: ` -mutation ReportAppliedKubernetesIntents ($namespace: String!, $intents: [IntentInput!]!, $clusterId: String!) { - reportAppliedKubernetesIntents(namespace: $namespace, intents: $intents, ossClusterId: $clusterId) -} -`, + Query: ReportAppliedKubernetesIntents_Operation, Variables: &__ReportAppliedKubernetesIntentsInput{ Namespace: namespace, Intents: intents, - ClusterId: clusterId, }, } - var err error + var err_ error - var data ReportAppliedKubernetesIntentsResponse - resp := &graphql.Response{Data: &data} + var data_ ReportAppliedKubernetesIntentsResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ +} + +// The query or mutation executed by ReportComponentStatus. +const ReportComponentStatus_Operation = ` +mutation ReportComponentStatus ($component: ComponentType!) { + reportIntegrationComponentStatus(component: $component) } +` func ReportComponentStatus( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, component ComponentType, ) (*ReportComponentStatusResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportComponentStatus", - Query: ` -mutation ReportComponentStatus ($component: ComponentType!) { - reportIntegrationComponentStatus(component: $component) -} -`, + Query: ReportComponentStatus_Operation, Variables: &__ReportComponentStatusInput{ Component: component, }, } - var err error + var err_ error - var data ReportComponentStatusResponse - resp := &graphql.Response{Data: &data} + var data_ ReportComponentStatusResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ } +// The query or mutation executed by ReportExternallyAccessibleServices. +const ReportExternallyAccessibleServices_Operation = ` +mutation ReportExternallyAccessibleServices ($namespace: String!, $services: [ExternallyAccessibleServiceInput!]!) { + reportExternallyAccessibleServices(namespace: $namespace, services: $services) +} +` + func ReportExternallyAccessibleServices( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, namespace string, services []ExternallyAccessibleServiceInput, ) (*ReportExternallyAccessibleServicesResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportExternallyAccessibleServices", - Query: ` -mutation ReportExternallyAccessibleServices ($namespace: String!, $services: [ExternallyAccessibleServiceInput!]!) { - reportExternallyAccessibleServices(namespace: $namespace, services: $services) -} -`, + Query: ReportExternallyAccessibleServices_Operation, Variables: &__ReportExternallyAccessibleServicesInput{ Namespace: namespace, Services: services, }, } - var err error + var err_ error - var data ReportExternallyAccessibleServicesResponse - resp := &graphql.Response{Data: &data} + var data_ ReportExternallyAccessibleServicesResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ +} + +// The query or mutation executed by ReportIntentsOperatorConfiguration. +const ReportIntentsOperatorConfiguration_Operation = ` +mutation ReportIntentsOperatorConfiguration ($configuration: IntentsOperatorConfigurationInput!) { + reportIntentsOperatorConfiguration(configuration: $configuration) } +` func ReportIntentsOperatorConfiguration( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, configuration IntentsOperatorConfigurationInput, ) (*ReportIntentsOperatorConfigurationResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportIntentsOperatorConfiguration", - Query: ` -mutation ReportIntentsOperatorConfiguration ($configuration: IntentsOperatorConfigurationInput!) { - reportIntentsOperatorConfiguration(configuration: $configuration) -} -`, + Query: ReportIntentsOperatorConfiguration_Operation, Variables: &__ReportIntentsOperatorConfigurationInput{ Configuration: configuration, }, } - var err error + var err_ error - var data ReportIntentsOperatorConfigurationResponse - resp := &graphql.Response{Data: &data} + var data_ ReportIntentsOperatorConfigurationResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ +} + +// The query or mutation executed by ReportKafkaServerConfig. +const ReportKafkaServerConfig_Operation = ` +mutation ReportKafkaServerConfig ($namespace: String!, $kafkaServerConfigs: [KafkaServerConfigInput!]!) { + reportKafkaServerConfigs(namespace: $namespace, serverConfigs: $kafkaServerConfigs) } +` func ReportKafkaServerConfig( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, namespace string, kafkaServerConfigs []KafkaServerConfigInput, ) (*ReportKafkaServerConfigResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportKafkaServerConfig", - Query: ` -mutation ReportKafkaServerConfig ($namespace: String!, $kafkaServerConfigs: [KafkaServerConfigInput!]!) { - reportKafkaServerConfigs(namespace: $namespace, serverConfigs: $kafkaServerConfigs) -} -`, + Query: ReportKafkaServerConfig_Operation, Variables: &__ReportKafkaServerConfigInput{ Namespace: namespace, KafkaServerConfigs: kafkaServerConfigs, }, } - var err error + var err_ error - var data ReportKafkaServerConfigResponse - resp := &graphql.Response{Data: &data} + var data_ ReportKafkaServerConfigResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ +} + +// The query or mutation executed by ReportNetworkPolicies. +const ReportNetworkPolicies_Operation = ` +mutation ReportNetworkPolicies ($namespace: String!, $policies: [NetworkPolicyInput!]!) { + reportNetworkPolicies(namespace: $namespace, policies: $policies) } +` func ReportNetworkPolicies( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, namespace string, policies []NetworkPolicyInput, ) (*ReportNetworkPoliciesResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportNetworkPolicies", - Query: ` -mutation ReportNetworkPolicies ($namespace: String!, $policies: [NetworkPolicyInput!]!) { - reportNetworkPolicies(namespace: $namespace, policies: $policies) -} -`, + Query: ReportNetworkPolicies_Operation, Variables: &__ReportNetworkPoliciesInput{ Namespace: namespace, Policies: policies, }, } - var err error + var err_ error - var data ReportNetworkPoliciesResponse - resp := &graphql.Response{Data: &data} + var data_ ReportNetworkPoliciesResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ } +// The query or mutation executed by ReportProtectedServicesSnapshot. +const ReportProtectedServicesSnapshot_Operation = ` +mutation ReportProtectedServicesSnapshot ($namespace: String!, $services: [ProtectedServiceInput!]!) { + reportProtectedServicesSnapshot(namespace: $namespace, services: $services) +} +` + func ReportProtectedServicesSnapshot( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, namespace string, services []ProtectedServiceInput, ) (*ReportProtectedServicesSnapshotResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "ReportProtectedServicesSnapshot", - Query: ` -mutation ReportProtectedServicesSnapshot ($namespace: String!, $services: [ProtectedServiceInput!]!) { - reportProtectedServicesSnapshot(namespace: $namespace, services: $services) -} -`, + Query: ReportProtectedServicesSnapshot_Operation, Variables: &__ReportProtectedServicesSnapshotInput{ Namespace: namespace, Services: services, }, } - var err error + var err_ error - var data ReportProtectedServicesSnapshotResponse - resp := &graphql.Response{Data: &data} + var data_ ReportProtectedServicesSnapshotResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ } -func dummy( - ctx context.Context, - client graphql.Client, -) (*dummyResponse, error) { - req := &graphql.Request{ - OpName: "dummy", - Query: ` +// The query or mutation executed by dummy. +const dummy_Operation = ` query dummy { dummyError } -`, +` + +func dummy( + ctx_ context.Context, + client_ graphql.Client, +) (*dummyResponse, error) { + req_ := &graphql.Request{ + OpName: "dummy", + Query: dummy_Operation, } - var err error + var err_ error - var data dummyResponse - resp := &graphql.Response{Data: &data} + var data_ dummyResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ } diff --git a/src/shared/otterizecloud/graphqlclient/genqlient.graphql b/src/shared/otterizecloud/graphqlclient/genqlient.graphql index e0fdfa1ec..a3b1ece68 100644 --- a/src/shared/otterizecloud/graphqlclient/genqlient.graphql +++ b/src/shared/otterizecloud/graphqlclient/genqlient.graphql @@ -3,8 +3,8 @@ mutation ReportKafkaServerConfig($namespace: String!, $kafkaServerConfigs: [Kafk } # @genqlient(pointer: true) -mutation ReportAppliedKubernetesIntents($namespace: String!,$intents: [IntentInput!]!, $clusterId: String!) { - reportAppliedKubernetesIntents(namespace: $namespace, intents: $intents, ossClusterId: $clusterId) +mutation ReportAppliedKubernetesIntents($namespace: String!,$intents: [IntentInput!]!) { + reportAppliedKubernetesIntents(namespace: $namespace, intents: $intents) } mutation ReportNetworkPolicies($namespace: String!, $policies: [NetworkPolicyInput!]!) { @@ -23,6 +23,11 @@ mutation ReportProtectedServicesSnapshot($namespace: String!, $services: [Protec reportProtectedServicesSnapshot(namespace: $namespace, services: $services) } + +mutation HandleDatabaseIntents($intents: [IntentInput!]!, $action: DBPermissionChange!) { + handleDatabaseIntents(intents: $intents, action: $action) +} + mutation ReportExternallyAccessibleServices($namespace: String!, $services: [ExternallyAccessibleServiceInput!]!) { reportExternallyAccessibleServices(namespace: $namespace, services: $services) } diff --git a/src/shared/otterizecloud/graphqlclient/schema.graphql b/src/shared/otterizecloud/graphqlclient/schema.graphql index 44a0239b9..14ec5fe8b 100644 --- a/src/shared/otterizecloud/graphqlclient/schema.graphql +++ b/src/shared/otterizecloud/graphqlclient/schema.graphql @@ -342,6 +342,11 @@ enum CustomConstraint { ID } +enum DBPermissionChange { + APPLY + DELETE +} + input DNSIPPairInput { dnsName: String! ips: [String!] @@ -359,13 +364,27 @@ input DatabaseConfigInput { operations: [DatabaseOperation!] } +type DatabaseCredentials { + username: String! + password: String! +} + +input DatabaseCredentialsInput { + username: String! + password: String! +} + type DatabaseInfo { + address: String! databaseType: DatabaseType! + credentials: DatabaseCredentials! visibility: DatabaseVisibilitySettings } input DatabaseInfoInput { + address: String! databaseType: DatabaseType! + credentials: DatabaseCredentialsInput! visibility: DatabaseVisibilitySettingsInput } @@ -627,21 +646,6 @@ type IDFilterValue { operator: IDFilterOperators! } -input IncomingInternetSourceInput { - ip: String! -} - -input IncomingTrafficDiscoveredIntentInput { - discoveredAt: Time! - intent: IncomingTrafficIntentInput! -} - -input IncomingTrafficIntentInput { - serverName: String! - namespace: String! - source: IncomingInternetSourceInput! -} - input InputAccessGraphFilter { clusterIds: InputIDFilterValue serviceIds: InputIDFilterValue @@ -1025,6 +1029,10 @@ type Mutation { name: String! databaseInfo: DatabaseInfoInput! ): Integration + updateDatabaseIntegrationCredentials( + integrationId: ID! + credentials: DatabaseCredentialsInput! + ): Boolean! """Create a new Kubernetes integration""" createKubernetesIntegration( environmentId: ID! @@ -1109,13 +1117,13 @@ type Mutation { reportExternalTrafficDiscoveredIntents( intents: [ExternalTrafficDiscoveredIntentInput!]! ): Boolean! - reportIncomingTrafficDiscoveredIntents( - intents: [IncomingTrafficDiscoveredIntentInput!]! - ): Boolean! reportAppliedKubernetesIntents( namespace: String! intents: [IntentInput!]! - ossClusterId: String + ): Boolean! + handleDatabaseIntents( + intents: [IntentInput!]! + action: DBPermissionChange! ): Boolean! reportNetworkPolicies( namespace: String! @@ -1305,6 +1313,10 @@ type Query { clusterId: ID name: String ): Integration! + testDatabaseConnection( + databaseInfo: DatabaseInfoInput! + integrationId: ID + ): TestDatabaseConnectionResponse! """Test database visibility connectivity""" testDatabaseVisibilityConnection( databaseInfo: DatabaseInfoInput! @@ -1449,6 +1461,7 @@ type Service { azureResource: AzureResource discoveredByIntegration: Integration tlsKeyPair: KeyPair! + userAndPassword: UserAndPassword! } type ServiceAccessGraph { @@ -1565,6 +1578,11 @@ type User { activeTutorial: UserTutorial! } +type UserAndPassword { + username: String! + password: String! +} + enum UserErrorType { UNAUTHENTICATED NOT_FOUND diff --git a/src/shared/otterizecloud/mocks/mock_cloud_api.go b/src/shared/otterizecloud/mocks/mock_cloud_api.go index 2d61d9e4a..05d8a66c2 100644 --- a/src/shared/otterizecloud/mocks/mock_cloud_api.go +++ b/src/shared/otterizecloud/mocks/mock_cloud_api.go @@ -35,6 +35,20 @@ func (m *MockCloudClient) EXPECT() *MockCloudClientMockRecorder { return m.recorder } +// ApplyDatabaseIntent mocks base method. +func (m *MockCloudClient) ApplyDatabaseIntent(ctx context.Context, intents []graphqlclient.IntentInput, action graphqlclient.DBPermissionChange) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyDatabaseIntent", ctx, intents, action) + ret0, _ := ret[0].(error) + return ret0 +} + +// ApplyDatabaseIntent indicates an expected call of ApplyDatabaseIntent. +func (mr *MockCloudClientMockRecorder) ApplyDatabaseIntent(ctx, intents, action interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyDatabaseIntent", reflect.TypeOf((*MockCloudClient)(nil).ApplyDatabaseIntent), ctx, intents, action) +} + // ReportAppliedIntents mocks base method. func (m *MockCloudClient) ReportAppliedIntents(ctx context.Context, namespace *string, intents []*graphqlclient.IntentInput) error { m.ctrl.T.Helper() diff --git a/src/shared/telemetries/telemetriesgql/generated.go b/src/shared/telemetries/telemetriesgql/generated.go index 39de09111..b88cb2abb 100644 --- a/src/shared/telemetries/telemetriesgql/generated.go +++ b/src/shared/telemetries/telemetriesgql/generated.go @@ -110,32 +110,35 @@ type __SendTelemetriesInput struct { // GetTelemetries returns __SendTelemetriesInput.Telemetries, and is useful for accessing the field via an interface. func (v *__SendTelemetriesInput) GetTelemetries() []TelemetryInput { return v.Telemetries } +// The query or mutation executed by SendTelemetries. +const SendTelemetries_Operation = ` +mutation SendTelemetries ($telemetries: [TelemetryInput!]!) { + sendTelemetries(telemetries: $telemetries) +} +` + func SendTelemetries( - ctx context.Context, - client graphql.Client, + ctx_ context.Context, + client_ graphql.Client, telemetries []TelemetryInput, ) (*SendTelemetriesResponse, error) { - req := &graphql.Request{ + req_ := &graphql.Request{ OpName: "SendTelemetries", - Query: ` -mutation SendTelemetries ($telemetries: [TelemetryInput!]!) { - sendTelemetries(telemetries: $telemetries) -} -`, + Query: SendTelemetries_Operation, Variables: &__SendTelemetriesInput{ Telemetries: telemetries, }, } - var err error + var err_ error - var data SendTelemetriesResponse - resp := &graphql.Response{Data: &data} + var data_ SendTelemetriesResponse + resp_ := &graphql.Response{Data: &data_} - err = client.MakeRequest( - ctx, - req, - resp, + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, ) - return &data, err + return &data_, err_ } diff --git a/src/shared/telemetries/telemetriesgql/schema.graphql b/src/shared/telemetries/telemetriesgql/schema.graphql index 44a0239b9..26d8a1c37 100644 --- a/src/shared/telemetries/telemetriesgql/schema.graphql +++ b/src/shared/telemetries/telemetriesgql/schema.graphql @@ -342,6 +342,11 @@ enum CustomConstraint { ID } +enum DBPermissionChange { + APPLY + DELETE +} + input DNSIPPairInput { dnsName: String! ips: [String!] @@ -359,13 +364,27 @@ input DatabaseConfigInput { operations: [DatabaseOperation!] } +type DatabaseCredentials { + username: String! + password: String! +} + +input DatabaseCredentialsInput { + username: String! + password: String! +} + type DatabaseInfo { + address: String! databaseType: DatabaseType! + credentials: DatabaseCredentials! visibility: DatabaseVisibilitySettings } input DatabaseInfoInput { + address: String! databaseType: DatabaseType! + credentials: DatabaseCredentialsInput! visibility: DatabaseVisibilitySettingsInput } @@ -627,21 +646,6 @@ type IDFilterValue { operator: IDFilterOperators! } -input IncomingInternetSourceInput { - ip: String! -} - -input IncomingTrafficDiscoveredIntentInput { - discoveredAt: Time! - intent: IncomingTrafficIntentInput! -} - -input IncomingTrafficIntentInput { - serverName: String! - namespace: String! - source: IncomingInternetSourceInput! -} - input InputAccessGraphFilter { clusterIds: InputIDFilterValue serviceIds: InputIDFilterValue @@ -801,7 +805,6 @@ type IntentsOperatorConfiguration { protectedServicesEnabled: Boolean! protectedServices: [Service!]! egressNetworkPolicyEnforcementEnabled: Boolean! - enforcedNamespaces: [String!] } input IntentsOperatorConfigurationInput { @@ -815,7 +818,6 @@ input IntentsOperatorConfigurationInput { gcpIAMPolicyEnforcementEnabled: Boolean azureIAMPolicyEnforcementEnabled: Boolean databaseEnforcementEnabled: Boolean - enforcedNamespaces: [String!] } type InternetConfig { @@ -1025,6 +1027,10 @@ type Mutation { name: String! databaseInfo: DatabaseInfoInput! ): Integration + updateDatabaseIntegrationCredentials( + integrationId: ID! + credentials: DatabaseCredentialsInput! + ): Boolean! """Create a new Kubernetes integration""" createKubernetesIntegration( environmentId: ID! @@ -1109,13 +1115,13 @@ type Mutation { reportExternalTrafficDiscoveredIntents( intents: [ExternalTrafficDiscoveredIntentInput!]! ): Boolean! - reportIncomingTrafficDiscoveredIntents( - intents: [IncomingTrafficDiscoveredIntentInput!]! - ): Boolean! reportAppliedKubernetesIntents( namespace: String! intents: [IntentInput!]! - ossClusterId: String + ): Boolean! + handleDatabaseIntents( + intents: [IntentInput!]! + action: DBPermissionChange! ): Boolean! reportNetworkPolicies( namespace: String! @@ -1305,6 +1311,10 @@ type Query { clusterId: ID name: String ): Integration! + testDatabaseConnection( + databaseInfo: DatabaseInfoInput! + integrationId: ID + ): TestDatabaseConnectionResponse! """Test database visibility connectivity""" testDatabaseVisibilityConnection( databaseInfo: DatabaseInfoInput! @@ -1449,6 +1459,7 @@ type Service { azureResource: AzureResource discoveredByIntegration: Integration tlsKeyPair: KeyPair! + userAndPassword: UserAndPassword! } type ServiceAccessGraph { @@ -1565,6 +1576,11 @@ type User { activeTutorial: UserTutorial! } +type UserAndPassword { + username: String! + password: String! +} + enum UserErrorType { UNAUTHENTICATED NOT_FOUND