diff --git a/go.mod b/go.mod index 04dc63139..a90bb9cdc 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( kmodules.xyz/client-go v0.29.6 kmodules.xyz/custom-resources v0.29.1 kmodules.xyz/monitoring-agent-api v0.29.0 - kubedb.dev/apimachinery v0.41.0-rc.1 + kubedb.dev/apimachinery v0.41.0-rc.1.0.20240131123101-c711b3abb2a0 kubedb.dev/db-client-go v0.0.9 sigs.k8s.io/controller-runtime v0.17.0 sigs.k8s.io/yaml v1.4.0 diff --git a/go.sum b/go.sum index 06a561cb9..7c77a78cf 100644 --- a/go.sum +++ b/go.sum @@ -819,8 +819,8 @@ kmodules.xyz/prober v0.29.0 h1:Ex7m4F9rH7uWNNJlLgP63ROOM+nUATJkC2L5OQ7nwMg= kmodules.xyz/prober v0.29.0/go.mod h1:UtK+HKyI1lFLEKX+HFLyOCVju6TO93zv3kwGpzqmKOo= kmodules.xyz/resource-metadata v0.18.2-0.20240105072614-e92a8a48d400 h1:bmd9/fvbhE55xtMF9hXVzjoUZFLGjMfEoHSHu9Hw6Gc= kmodules.xyz/resource-metadata v0.18.2-0.20240105072614-e92a8a48d400/go.mod h1:XsCdEKjfoulX29tMGviDhjT/jLl158uvMvXlKOhK1as= -kubedb.dev/apimachinery v0.41.0-rc.1 h1:+aaSLuMXcBlQXuaJMhHRalqOIKrE1pReBwyV3YwdpSo= -kubedb.dev/apimachinery v0.41.0-rc.1/go.mod h1:Z6vywQE35f+j6Vh24OaY2q5jnwAirDlx70nuKmlrrN0= +kubedb.dev/apimachinery v0.41.0-rc.1.0.20240131123101-c711b3abb2a0 h1:pgtLsEzFPYzRXCNHKiuczfbwhYi5XyRrvT9ijDB2nWI= +kubedb.dev/apimachinery v0.41.0-rc.1.0.20240131123101-c711b3abb2a0/go.mod h1:Z6vywQE35f+j6Vh24OaY2q5jnwAirDlx70nuKmlrrN0= kubedb.dev/db-client-go v0.0.9 h1:oYfNBjZRLtF5jij1u83NW0yCjz1Is4zE9RI0tG0h5AU= kubedb.dev/db-client-go v0.0.9/go.mod h1:h37/SUuec3Jnxusxv7JTs3Vl5SX9C/rNPG7qB7hQq4M= kubeops.dev/sidekick v0.0.5-0.20231225214445-a15c70833046 h1:X1ieV+IXqNesmFwSH6NEVF1J2wO0vplC4k6v3Vmq0d0= diff --git a/pkg/cmds/alert.go b/pkg/cmds/alert.go deleted file mode 100644 index 34a0d0d83..000000000 --- a/pkg/cmds/alert.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright AppsCode Inc. and Contributors -Licensed under the AppsCode Community License 1.0.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md -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 cmds - -import ( - "kubedb.dev/cli/pkg/alerts" - - "github.com/spf13/cobra" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" -) - -var ( - alertLong = templates.LongDesc(` - Get the prometheus alerts for a specific database in just one command - `) - alertExample = templates.Examples(` - kubectl dba get-alerts mongodb -n demo sample-mongodb --prom-svc-name=prometheus-kube-prometheus-prometheus --prom-svc-namespace=monitoring - - Valid resource types include: - * elasticsearch - * mongodb - * mariadb - * mysql - * postgres - * redis -`) -) - -func NewCmdAlert(f cmdutil.Factory) *cobra.Command { - var prom alerts.PromSvc - cmd := &cobra.Command{ - Use: "get-alerts", - Short: i18n.T("Alerts associated with a database"), - Long: alertLong, - Example: alertExample, - Run: func(cmd *cobra.Command, args []string) { - alerts.Run(f, args, prom) - }, - DisableFlagsInUseLine: true, - DisableAutoGenTag: true, - } - cmd.Flags().StringVarP(&prom.Name, "prom-svc-name", "", "", "name of the prometheus service") - cmd.Flags().StringVarP(&prom.Namespace, "prom-svc-namespace", "", "", "namespace of the prometheus service") - cmd.Flags().IntVarP(&prom.Port, "prom-svc-port", "", 9090, "port of the prometheus service") - return cmd -} diff --git a/pkg/cmds/grafana_dashboard.go b/pkg/cmds/grafana_dashboard.go deleted file mode 100644 index 6ea70dac7..000000000 --- a/pkg/cmds/grafana_dashboard.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright AppsCode Inc. and Contributors - -Licensed under the AppsCode Community License 1.0.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md - -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 cmds - -import ( - "kubedb.dev/cli/pkg/dashboard" - - "github.com/spf13/cobra" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" -) - -var dashboardLong = templates.LongDesc(` - Check availability of metrics in prometheus server used in a grafana dashboard. - `) - -var dashboardExample = templates.Examples(` - # Check availability of mongodb-summary-dashboard grafana dashboard of mongodb - kubectl dba dashboard mongodb mongodb-summary-dashboard - - Valid dashboards include: - * elasticsearch - * mongodb - * mariadb - * mysql - * postgres - * redis -`) - -func NewCmdDashboard(f cmdutil.Factory) *cobra.Command { - var branch string - var prom dashboard.PromSvc - cmd := &cobra.Command{ - Use: "dashboard", - Short: i18n.T("Check availability of a grafana dashboard"), - Long: dashboardLong, - - Run: func(cmd *cobra.Command, args []string) { - dashboard.Run(f, args, branch, prom) - }, - Example: dashboardExample, - DisableFlagsInUseLine: true, - DisableAutoGenTag: true, - } - cmd.Flags().StringVarP(&branch, "branch", "b", "master", "branch name of the github repo") - cmd.Flags().StringVarP(&prom.Name, "prom-svc-name", "", "", "name of the prometheus service") - cmd.Flags().StringVarP(&prom.Namespace, "prom-svc-namespace", "", "", "namespace of the prometheus service") - cmd.Flags().IntVarP(&prom.Port, "prom-svc-port", "", 9090, "port of the prometheus service") - return cmd -} diff --git a/pkg/cmds/monitor.go b/pkg/cmds/monitor.go new file mode 100644 index 000000000..fe181bb0e --- /dev/null +++ b/pkg/cmds/monitor.go @@ -0,0 +1,199 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 cmds + +import ( + "kubedb.dev/cli/pkg/monitor" + "kubedb.dev/cli/pkg/monitor/alerts" + "kubedb.dev/cli/pkg/monitor/connection" + "kubedb.dev/cli/pkg/monitor/dashboard" + + "github.com/spf13/cobra" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var prom monitor.PromSvc + +var monitorLong = templates.LongDesc(` + All monitoring related commands from AppsCode. + `) + +var monitorExample = templates.Examples(` + + # Check triggered alerts for a specific database + kubectl dba monitor get-alerts [DATABASE] [DATABASE_NAME] -n [NAMESPACE] + + # Check availability of grafana dashboard of a database + kubectl dba monitor dashboard [DATABASE] [DASHBOARD_NAME] + + # Check connection status of target with prometheus server for a specific database + kubectl dba monitor check-connection [DATABASE] [DATABASE_NAME] -n [NAMESPACE] + + # Common Flags + --prom-svc-name : name of the prometheus service + --prom-svc-namespace : namespace of the prometheus service + --prom-svc-port : port of the prometheus service + +`) + +func NewCmdMonitor(f cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "monitor", + Short: i18n.T("Monitoring related commands for a database"), + Long: monitorLong, + Example: monitorExample, + Run: func(cmd *cobra.Command, args []string) { + }, + DisableFlagsInUseLine: true, + DisableAutoGenTag: true, + } + + cmd.PersistentFlags().StringVarP(&prom.Name, "prom-svc-name", "", "", "name of the prometheus service") + cmd.PersistentFlags().StringVarP(&prom.Namespace, "prom-svc-namespace", "", "", "namespace of the prometheus service") + cmd.PersistentFlags().IntVarP(&prom.Port, "prom-svc-port", "", 9090, "port of the prometheus service") + + cmd.AddCommand(DashboardCMD(f)) + cmd.AddCommand(AlertCMD(f)) + cmd.AddCommand(ConnectionCMD(f)) + + return cmd +} + +// alert +var alertLong = templates.LongDesc(` + Get the prometheus alerts for a specific database in just one command + `) + +var alertExample = templates.Examples(` + kubectl dba monitor get-alerts [DATABASE] [DATABASE_NAME] -n [NAMESPACE] \ + --prom-svc=[PROM_SVC_NAME] --prom-svc-namespace=[PROM_SVC_NS] --prom-svc-port=[PROM_SVC_PORT] + + # Get triggered alert for a specific mongodb + kubectl dba monitor get-alerts mongodb sample-mongodb -n demo \ + --prom-svc-name=prometheus-kube-prometheus-prometheus --prom-svc-namespace=monitoring --prom-svc-port=9090 + + Valid resource types include: + * elasticsearch + * kafka + * mariadb + * mongodb + * mysql + * perconaxtradb + * postgres + * proxysql + * redis +`) + +func AlertCMD(f cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "get-alerts", + Short: i18n.T("Alerts associated with a database"), + Long: alertLong, + Example: alertExample, + Run: func(cmd *cobra.Command, args []string) { + alerts.Run(f, args, prom) + }, + DisableFlagsInUseLine: true, + DisableAutoGenTag: true, + } + return cmd +} + +// dashboard +var dashboardLong = templates.LongDesc(` + Check availability of metrics in prometheus server used in a grafana dashboard. + `) + +var dashboardExample = templates.Examples(` + kubectl dba monitor dashboard [DATABASE] [DASHBOARD_NAME] \ + --prom-svc=[PROM_SVC_NAME] --prom-svc-namespace=[PROM_SVC_NS] --prom-svc-port=[PROM_SVC_PORT] + + # Check availability of a postgres grafana dashboard + kubectl-dba monitor dashboard postgres postgres_databases_dashboard \ + --prom-svc-name=prometheus-kube-prometheus-prometheus --prom-svc-namespace=monitoring --prom-svc-port=9090 + + Valid dashboards include: + * elasticsearch + * kafka + * mariadb + * mongodb + * mysql + * perconaxtradb + * postgres + * proxysql + * redis +`) + +func DashboardCMD(f cmdutil.Factory) *cobra.Command { + var branch string + cmd := &cobra.Command{ + Use: "dashboard", + Short: i18n.T("Check availability of a grafana dashboard"), + Long: dashboardLong, + + Run: func(cmd *cobra.Command, args []string) { + dashboard.Run(f, args, branch, prom) + }, + Example: dashboardExample, + DisableFlagsInUseLine: true, + DisableAutoGenTag: true, + } + cmd.Flags().StringVarP(&branch, "branch", "b", "master", "branch name of the github repo") + return cmd +} + +// check-connection +var connectionLong = templates.LongDesc(` + Check connection status for different targets with prometheus server for specific DB. +`) + +var connectionExample = templates.Examples(` + kubectl dba monitor check-connection [DATABASE] [DATABASE_NAME] -n [NAMESPACE] \ + --prom-svc=[PROM_SVC_NAME] --prom-svc-namespace=[PROM_SVC_NS] --prom-svc-port=[PROM_SVC_PORT] + + # Check connection status for different targets with prometheus server for a specific postgres database + kubectl dba monitor check-connection mongodb sample_mg -n demo \ + --prom-svc-name=prometheus-kube-prometheus-prometheus --prom-svc-namespace=monitoring --prom-svc-port=9090 + + Valid resource types include: + * elasticsearch + * kafka + * mariadb + * mongodb + * mysql + * perconaxtradb + * postgres + * proxysql + * redis +`) + +func ConnectionCMD(f cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "check-connection", + Short: i18n.T("Check connection status of prometheus targets with server"), + Long: connectionLong, + Example: connectionExample, + Run: func(cmd *cobra.Command, args []string) { + connection.Run(f, args, prom) + }, + DisableFlagsInUseLine: true, + DisableAutoGenTag: true, + } + return cmd +} diff --git a/pkg/cmds/root.go b/pkg/cmds/root.go index 7e92b68a7..1024ca308 100644 --- a/pkg/cmds/root.go +++ b/pkg/cmds/root.go @@ -89,12 +89,6 @@ func NewKubeDBCommand(in io.Reader, out, err io.Writer) *cobra.Command { NewCmdExec(f), }, }, - { - Message: "Get Alerts for specific database", - Commands: []*cobra.Command{ - NewCmdAlert(f), - }, - }, { Message: "Insert and Verify data in Database", Commands: []*cobra.Command{ @@ -114,9 +108,9 @@ func NewKubeDBCommand(in io.Reader, out, err io.Writer) *cobra.Command { }, }, { - Message: "Check availability of metrics", + Message: "Metric related CMDs", Commands: []*cobra.Command{ - NewCmdDashboard(f), + NewCmdMonitor(f), }, }, } diff --git a/pkg/alerts/db.go b/pkg/monitor/alerts/db.go similarity index 70% rename from pkg/alerts/db.go rename to pkg/monitor/alerts/db.go index 55efe30be..4ab694d9c 100644 --- a/pkg/alerts/db.go +++ b/pkg/monitor/alerts/db.go @@ -20,10 +20,11 @@ import ( "context" "fmt" "log" - "strings" "time" - promapi "github.com/prometheus/client_golang/api" + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + "kubedb.dev/cli/pkg/monitor" + promv1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,12 +38,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -type PromSvc struct { - Name string - Namespace string - Port int -} - type dbOpts struct { db client.Object config *rest.Config @@ -50,12 +45,7 @@ type dbOpts struct { resource string } -const ( - kubeDBGroup = "kubedb.com" - kubeDBVersion = "v1alpha2" -) - -func Run(f cmdutil.Factory, args []string, prom PromSvc) { +func Run(f cmdutil.Factory, args []string, prom monitor.PromSvc) { if len(args) < 2 { log.Fatal("Enter db object's name as an argument") } @@ -67,43 +57,20 @@ func Run(f cmdutil.Factory, args []string, prom PromSvc) { _ = fmt.Errorf("failed to get current namespace") } - opts, err := newDBOpts(f, dbName, namespace, convertedResource(resource)) + opts, err := newDBOpts(f, dbName, namespace, monitor.ConvertedResourceToPlural(resource)) if err != nil { log.Fatalln(err) } - p, err := opts.ForwardPort("services", prom) - if err != nil { - log.Fatalln(err) - } - err = opts.work(p) + promClient, tunnel := monitor.GetPromClientAndTunnel(opts.config, prom) + defer tunnel.Close() + + err = opts.work(promClient) if err != nil { log.Fatalln(err) } } -func convertedResource(resource string) string { - // standardizing the resource name - res := strings.ToLower(resource) - switch res { - case "es", "elasticsearch", "elasticsearches": - res = "elasticsearches" - case "md", "mariadb", "mariadbs": - res = "mariadbs" - case "mg", "mongodb", "mongodbs": - res = "mongodbs" - case "my", "mysql", "mysqls": - res = "mysqls" - case "pg", "postgres", "postgreses": - res = "postgreses" - case "rd", "redis", "redises": - res = "redises" - default: - fmt.Printf("%s is not a valid resource type \n", resource) - } - return res -} - func newDBOpts(f cmdutil.Factory, dbName, namespace, resource string) (*dbOpts, error) { config, err := f.ToRESTConfig() if err != nil { @@ -115,7 +82,8 @@ func newDBOpts(f cmdutil.Factory, dbName, namespace, resource string) (*dbOpts, return nil, err } - dbRes := schema.GroupVersionResource{Group: kubeDBGroup, Version: kubeDBVersion, Resource: resource} + gvk := api.SchemeGroupVersion + dbRes := schema.GroupVersionResource{Group: gvk.Group, Version: gvk.Version, Resource: resource} db, err := dc.Resource(dbRes).Namespace(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) if err != nil { return nil, err @@ -135,7 +103,7 @@ func newDBOpts(f cmdutil.Factory, dbName, namespace, resource string) (*dbOpts, return opts, nil } -func (opts *dbOpts) ForwardPort(resource string, prom PromSvc) (*portforward.Tunnel, error) { +func (opts *dbOpts) ForwardPort(resource string, prom monitor.PromSvc) (*portforward.Tunnel, error) { tunnel := portforward.NewTunnel(portforward.TunnelOptions{ Client: opts.kubeClient.CoreV1().RESTClient(), Config: opts.config, @@ -151,15 +119,7 @@ func (opts *dbOpts) ForwardPort(resource string, prom PromSvc) (*portforward.Tun return tunnel, nil } -func (opts *dbOpts) work(p *portforward.Tunnel) error { - pc, err := promapi.NewClient(promapi.Config{ - Address: fmt.Sprintf("http://localhost:%d", p.Local), - }) - if err != nil { - return err - } - - promAPI := promv1.NewAPI(pc) +func (opts *dbOpts) work(promAPI promv1.API) error { alertQuery := fmt.Sprintf("ALERTS{alertstate=\"firing\",k8s_group=\"kubedb.com\",k8s_resource=\"%s\",app=\"%s\",app_namespace=\"%s\"}", opts.resource, opts.db.GetName(), opts.db.GetNamespace()) result, warnings, err := promAPI.QueryRange(context.TODO(), alertQuery, promv1.Range{ diff --git a/pkg/monitor/connection/db.go b/pkg/monitor/connection/db.go new file mode 100644 index 000000000..2b8d83c76 --- /dev/null +++ b/pkg/monitor/connection/db.go @@ -0,0 +1,108 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 connection + +import ( + "context" + "fmt" + "log" + "time" + + "kubedb.dev/cli/pkg/monitor" + + "github.com/prometheus/common/model" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +// kubectl dba monitor check-connection mongodb -n demo sample_mg -> all connection check and report + +type metrics struct { + metric string + label string + labelValue string +} + +func Run(f cmdutil.Factory, args []string, prom monitor.PromSvc) { + if len(args) < 2 { + log.Fatal("Enter database and specific database name as argument") + } + + database := monitor.ConvertedResourceToSingular(args[0]) + databaseName := args[1] + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + config, err := f.ToRESTConfig() + if err != nil { + log.Fatal(err) + } + + promClient, tunnel := monitor.GetPromClientAndTunnel(config, prom) + defer tunnel.Close() + + queries := getIdenticalMetrics(database, databaseName) + var notFound []string + + for target, query := range queries { + metricName := query.metric + endTime := time.Now() + + result, _, err := promClient.Query(context.TODO(), metricName, endTime) + if err != nil { + log.Fatal("Error querying Prometheus:", err, " metric: ", metricName) + } + + matrix := result.(model.Vector) + + if len(matrix) > 0 { + if query.label != "" { + // DB and panopticon related metrics.So we need to check label also + // Check if the label exists for any result in the matrix + exist := false + for _, sample := range matrix { + if sample.Metric != nil { + if labelVal, ok := sample.Metric[model.LabelName(query.label)]; ok { + if string(labelVal) == query.labelValue { + if namespaceVal, ok := sample.Metric[model.LabelName("namespace")]; ok { + if string(namespaceVal) == namespace { + exist = true + } + } + } + } + } + } + if !exist { + notFound = append(notFound, target) + } + } + } else { + notFound = append(notFound, target) + } + } + + if len(notFound) == 0 { + fmt.Printf("All monitoring connection established successfully for %s : %s/%s\n", database, namespace, databaseName) + } else { + for _, target := range notFound { + fmt.Printf("%s monitoring connection not found for %s : %s/%s\n", target, database, namespace, databaseName) + } + } +} diff --git a/pkg/monitor/connection/helper.go b/pkg/monitor/connection/helper.go new file mode 100644 index 000000000..d801dd2a2 --- /dev/null +++ b/pkg/monitor/connection/helper.go @@ -0,0 +1,114 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 connection + +import ( + "fmt" + "log" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" +) + +const ( + cAdvisorMetric = "container_cpu_usage_seconds_total" + kubeletMetric = "kubelet_active_pods" + ksmMetric = "kube_pod_status_phase" + nodeExporterMetric = "node_cpu_seconds_total" +) + +func getIdenticalMetrics(database, databaseName string) map[string]*metrics { + queries := make(map[string]*metrics) + queries["cAdvisor"] = &metrics{metric: cAdvisorMetric} + queries["kubelet"] = &metrics{metric: kubeletMetric} + queries["kube-state-metric"] = &metrics{metric: ksmMetric} + queries["node-exporter"] = &metrics{metric: nodeExporterMetric} + + queries = getDBMetrics(database, databaseName, queries) + + return queries +} + +func getDBMetrics(database, name string, queries map[string]*metrics) map[string]*metrics { + label := "service" + labelValue := fmt.Sprintf("%s-stats", name) + switch database { + case api.ResourceSingularElasticsearch: + queries[database] = &metrics{ + metric: "elasticsearch_clusterinfo_up", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularKafka: + queries[database] = &metrics{ + metric: "kafka_controller_kafkacontroller_activebrokercount", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularMariaDB: + queries[database] = &metrics{ + metric: "mysql_up", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularMongoDB: + queries[database] = &metrics{ + metric: "mongodb_up", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularMySQL: + queries[database] = &metrics{ + metric: "mysql_up", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularPerconaXtraDB: + queries[database] = &metrics{ + metric: "mysql_up", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularPostgres: + queries[database] = &metrics{ + metric: "pg_up", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularProxySQL: + queries[database] = &metrics{ + metric: "proxysql_uptime_seconds_total", + label: label, + labelValue: labelValue, + } + case api.ResourceSingularRedis: + queries[database] = &metrics{ + metric: "redis_up", + label: label, + labelValue: labelValue, + } + default: + log.Fatal("database invalid!") + } + + // Panopticon + queries["panopticon"] = &metrics{ + metric: fmt.Sprintf("kubedb_com_%s_info", database), + label: database, + labelValue: name, + } + return queries +} diff --git a/pkg/dashboard/db.go b/pkg/monitor/dashboard/db.go similarity index 89% rename from pkg/dashboard/db.go rename to pkg/monitor/dashboard/db.go index e75efa4c6..247146187 100644 --- a/pkg/dashboard/db.go +++ b/pkg/monitor/dashboard/db.go @@ -20,11 +20,10 @@ import ( "context" "fmt" "log" - "strconv" "strings" "time" - "kubedb.dev/cli/pkg/lib" + "kubedb.dev/cli/pkg/monitor" "github.com/prometheus/common/model" cmdutil "k8s.io/kubectl/pkg/cmd/util" @@ -35,22 +34,18 @@ type queryOpts struct { panelTitle string labelNames []string } + type missingOpts struct { labelName []string panelTitle []string } -type PromSvc struct { - Name string - Namespace string - Port int -} -func Run(f cmdutil.Factory, args []string, branch string, prom PromSvc) { +func Run(f cmdutil.Factory, args []string, branch string, prom monitor.PromSvc) { if len(args) < 2 { log.Fatal("Enter database and grafana dashboard name as argument") } - database := args[0] + database := monitor.ConvertedResourceToSingular(args[0]) dashboard := args[1] url := getURL(branch, database, dashboard) @@ -64,14 +59,9 @@ func Run(f cmdutil.Factory, args []string, branch string, prom PromSvc) { log.Fatal(err) } // Port forwarding cluster prometheus service for that grafana dashboard's prom datasource. - tunnel, err := lib.TunnelToDBService(config, prom.Name, prom.Namespace, prom.Port) - if err != nil { - log.Fatal(err) - } + promClient, tunnel := monitor.GetPromClientAndTunnel(config, prom) defer tunnel.Close() - promClient := getPromClient(strconv.Itoa(tunnel.Local)) - // var unknown []missingOpts unknown := make(map[string]*missingOpts) diff --git a/pkg/dashboard/helper.go b/pkg/monitor/dashboard/helper.go similarity index 79% rename from pkg/dashboard/helper.go rename to pkg/monitor/dashboard/helper.go index 96b577af8..7da7d35ac 100644 --- a/pkg/dashboard/helper.go +++ b/pkg/monitor/dashboard/helper.go @@ -22,9 +22,6 @@ import ( "io" "log" "net/http" - - "github.com/prometheus/client_golang/api" - v1 "github.com/prometheus/client_golang/api/prometheus/v1" ) func getURL(branch, database, dashboard string) string { @@ -53,20 +50,6 @@ func getDashboard(url string) map[string]interface{} { return dashboardData } -func getPromClient(localPort string) v1.API { - prometheusURL := fmt.Sprintf("http://localhost:%s/", localPort) - - client, err := api.NewClient(api.Config{ - Address: prometheusURL, - }) - if err != nil { - log.Fatal("Error creating Prometheus client:", err) - } - - // Create a new Prometheus API client - return v1.NewAPI(client) -} - func uniqueAppend(slice []string, valueToAdd string) []string { for _, existingValue := range slice { if existingValue == valueToAdd { diff --git a/pkg/dashboard/parser.go b/pkg/monitor/dashboard/parser.go similarity index 100% rename from pkg/dashboard/parser.go rename to pkg/monitor/dashboard/parser.go diff --git a/pkg/monitor/prometheus_setup.go b/pkg/monitor/prometheus_setup.go new file mode 100644 index 000000000..e1f70fcb3 --- /dev/null +++ b/pkg/monitor/prometheus_setup.go @@ -0,0 +1,59 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 monitor + +import ( + "fmt" + "log" + "strconv" + + "kubedb.dev/cli/pkg/lib" + + "github.com/prometheus/client_golang/api" + promv1 "github.com/prometheus/client_golang/api/prometheus/v1" + "k8s.io/client-go/rest" + "kmodules.xyz/client-go/tools/portforward" +) + +type PromSvc struct { + Name string + Namespace string + Port int +} + +func GetPromClientAndTunnel(config *rest.Config, prom PromSvc) (promv1.API, *portforward.Tunnel) { + tunnel, err := lib.TunnelToDBService(config, prom.Name, prom.Namespace, prom.Port) + if err != nil { + log.Fatal(err) + } + + promClient := getPromClient(strconv.Itoa(tunnel.Local)) + return promClient, tunnel +} + +func getPromClient(localPort string) promv1.API { + prometheusURL := fmt.Sprintf("http://localhost:%s/", localPort) + + client, err := api.NewClient(api.Config{ + Address: prometheusURL, + }) + if err != nil { + log.Fatal("Error creating Prometheus client:", err) + } + + return promv1.NewAPI(client) +} diff --git a/pkg/monitor/util.go b/pkg/monitor/util.go new file mode 100644 index 000000000..db7427218 --- /dev/null +++ b/pkg/monitor/util.go @@ -0,0 +1,80 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 monitor + +import ( + "log" + "strings" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" +) + +func ConvertedResourceToPlural(resource string) string { + // standardizing the resource name + res := strings.ToLower(resource) + switch res { + case api.ResourceCodeElasticsearch, api.ResourcePluralElasticsearch, api.ResourceSingularElasticsearch: + res = api.ResourcePluralElasticsearch + case api.ResourceCodeKafka, api.ResourcePluralKafka, api.ResourceSingularKafka: + res = api.ResourcePluralKafka + case api.ResourceCodeMariaDB, api.ResourcePluralMariaDB, api.ResourceSingularMariaDB: + res = api.ResourcePluralMariaDB + case api.ResourceCodeMongoDB, api.ResourcePluralMongoDB, api.ResourceSingularMongoDB: + res = api.ResourcePluralMongoDB + case api.ResourceCodeMySQL, api.ResourcePluralMySQL, api.ResourceSingularMySQL: + res = api.ResourcePluralMySQL + case api.ResourceCodePerconaXtraDB, api.ResourcePluralPerconaXtraDB, api.ResourceSingularPerconaXtraDB: + res = api.ResourcePluralPerconaXtraDB + case api.ResourceCodePostgres, api.ResourcePluralPostgres, api.ResourceSingularPostgres: + res = api.ResourcePluralPostgres + case api.ResourceCodeProxySQL, api.ResourcePluralProxySQL, api.ResourceSingularProxySQL: + res = api.ResourcePluralProxySQL + case api.ResourceCodeRedis, api.ResourcePluralRedis, api.ResourceSingularRedis: + res = api.ResourcePluralRedis + default: + log.Fatalf("%s is not a valid resource type \n", resource) + } + return res +} + +func ConvertedResourceToSingular(resource string) string { + // standardizing the resource name + res := strings.ToLower(resource) + switch res { + case api.ResourceCodeElasticsearch, api.ResourcePluralElasticsearch, api.ResourceSingularElasticsearch: + res = api.ResourceSingularElasticsearch + case api.ResourceCodeKafka, api.ResourcePluralKafka, api.ResourceSingularKafka: + res = api.ResourceSingularKafka + case api.ResourceCodeMariaDB, api.ResourcePluralMariaDB, api.ResourceSingularMariaDB: + res = api.ResourceSingularMariaDB + case api.ResourceCodeMongoDB, api.ResourcePluralMongoDB, api.ResourceSingularMongoDB: + res = api.ResourceSingularMongoDB + case api.ResourceCodeMySQL, api.ResourcePluralMySQL, api.ResourceSingularMySQL: + res = api.ResourceSingularMySQL + case api.ResourceCodePerconaXtraDB, api.ResourcePluralPerconaXtraDB, api.ResourceSingularPerconaXtraDB: + res = api.ResourceSingularPerconaXtraDB + case api.ResourceCodePostgres, api.ResourcePluralPostgres, api.ResourceSingularPostgres: + res = api.ResourceSingularPostgres + case api.ResourceCodeProxySQL, api.ResourcePluralProxySQL, api.ResourceSingularProxySQL: + res = api.ResourceSingularProxySQL + case api.ResourceCodeRedis, api.ResourcePluralRedis, api.ResourceSingularRedis: + res = api.ResourceSingularRedis + default: + log.Fatalf("%s is not a valid resource type \n", resource) + } + return res +} diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/druid_helpers.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/druid_helpers.go index 02b94210b..71dd352b6 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/druid_helpers.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/druid_helpers.go @@ -26,6 +26,7 @@ import ( "kubedb.dev/apimachinery/apis/kubedb" "kubedb.dev/apimachinery/crds" + "github.com/Masterminds/semver/v3" "gomodules.xyz/pointer" v1 "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -316,66 +317,85 @@ func (d *Druid) SetDefaults() { return } + version, err := semver.NewVersion(druidVersion.Spec.Version) + if err != nil { + klog.Errorf("failed to parse druid version :%s\n", err.Error()) + return + } + if d.Spec.Topology != nil { if d.Spec.Topology.Coordinators != nil { if d.Spec.Topology.Coordinators.Replicas == nil { d.Spec.Topology.Coordinators.Replicas = pointer.Int32P(1) } - if d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext == nil { - d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + if version.Major() > 25 { + if d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext == nil { + d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + } + d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate) + d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate) } - d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate) - d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate) } if d.Spec.Topology.Overlords != nil { if d.Spec.Topology.Overlords.Replicas == nil { d.Spec.Topology.Overlords.Replicas = pointer.Int32P(1) } - if d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext == nil { - d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + if version.Major() > 25 { + if d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext == nil { + d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + } + d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate) + d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate) } - d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate) - d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate) } if d.Spec.Topology.MiddleManagers != nil { if d.Spec.Topology.MiddleManagers.Replicas == nil { d.Spec.Topology.MiddleManagers.Replicas = pointer.Int32P(1) } - if d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext == nil { - d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + if version.Major() > 25 { + if d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext == nil { + d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + } + d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate) + d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate) } - d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate) - d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate) } if d.Spec.Topology.Historicals != nil { if d.Spec.Topology.Historicals.Replicas == nil { d.Spec.Topology.Historicals.Replicas = pointer.Int32P(1) } - if d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext == nil { - d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + if d.Spec.Version > "25.0.0" { + if d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext == nil { + d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + } + d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate) + d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate) } - d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate) - d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate) } if d.Spec.Topology.Brokers != nil { if d.Spec.Topology.Brokers.Replicas == nil { d.Spec.Topology.Brokers.Replicas = pointer.Int32P(1) } - if d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext == nil { - d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + if version.Major() > 25 { + if d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext == nil { + d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + } + d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate) + d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate) + } - d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate) - d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate) } if d.Spec.Topology.Routers != nil { if d.Spec.Topology.Routers.Replicas == nil { d.Spec.Topology.Routers.Replicas = pointer.Int32P(1) } - if d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext == nil { - d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + if version.Major() > 25 { + if d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext == nil { + d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser} + } + d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate) + d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate) } - d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate) - d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate) } } if d.Spec.MetadataStorage != nil { diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_helpers.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_helpers.go index 62058fdfe..06ccfbee6 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_helpers.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_helpers.go @@ -222,9 +222,15 @@ func (f *FerretDB) SetDefaults() { } defaultVersion := "13.13" - if !f.Spec.Backend.ExternallyManaged && f.Spec.Backend.Postgres == nil { - f.Spec.Backend.Postgres = &PostgresRef{ - Version: &defaultVersion, + if !f.Spec.Backend.ExternallyManaged { + if f.Spec.Backend.Postgres == nil { + f.Spec.Backend.Postgres = &PostgresRef{ + Version: &defaultVersion, + } + } else { + if f.Spec.Backend.Postgres.Version == nil { + f.Spec.Backend.Postgres.Version = &defaultVersion + } } } f.SetTLSDefaults() diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_types.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_types.go index c465967c5..7e0afb65f 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_types.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_types.go @@ -148,12 +148,15 @@ type PostgresRef struct { } type PostgresServiceRef struct { - Name *string `json:"name"` - Namespace *string `json:"namespace"` + // +optional + Name string `json:"name,omitempty"` + // +optional + Namespace string `json:"namespace,omitempty"` // PgPort is used because the service referred to the // pg pod can have any port between 1 and 65535, inclusive // but targetPort is fixed to 5432 - PgPort *string `json:"pgPort"` + // +optional + PgPort int32 `json:"pgPort,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_webhook.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_webhook.go index 2d8695f92..c27e69500 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_webhook.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_webhook.go @@ -156,6 +156,7 @@ func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList { `'spec.authSecret.name' needs to specify when auth secret is externally managed`)) } + // Termination policy related if f.Spec.StorageType == StorageTypeEphemeral && f.Spec.TerminationPolicy == TerminationPolicyHalt { allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"), f.Name, @@ -173,22 +174,26 @@ func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList { allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"), f.Name, `'spec.postgres' is missing when backend is externally managed`)) + } else { + if f.Spec.Backend.Postgres.URL == nil { + err := f.validateServiceRef(f.Spec.Backend.Postgres.Service) + allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"), + f.Name, + err.Error())) + } } - if f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.URL == nil && f.Spec.Backend.Postgres.Service == nil { - allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"), - f.Name, - `Have to provide 'backend.postgres.url' or 'backend.postgres.service' when backend is externally managed`)) - } - } - if !f.Spec.Backend.ExternallyManaged && f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.Version != nil { - err := f.validatePostgresVersion() - if err != nil { - allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"), - f.Name, - err.Error())) + } else { + if f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.Version != nil { + err := f.validatePostgresVersion() + if err != nil { + allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"), + f.Name, + err.Error())) + } } } + // TLS related if f.Spec.SSLMode == SSLModeAllowSSL || f.Spec.SSLMode == SSLModePreferSSL { allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("sslMode"), f.Name, @@ -239,3 +244,14 @@ func (f *FerretDB) validatePostgresVersion() error { } return nil } + +func (f *FerretDB) validateServiceRef(ref *PostgresServiceRef) error { + if ref == nil { + return errors.New(`have to provide 'backend.postgres.url' or 'backend.postgres.service' when backend is externally managed`) + } + // port needs to be 0 < x < 65536 + if ref.Namespace == "" || ref.Name == "" || ref.PgPort <= 0 || ref.PgPort >= 65536 { + return errors.New("pg service reference name, namespace and port(0