diff --git a/go.mod b/go.mod index 0865b1f9f..4797347e0 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( kmodules.xyz/monitoring-agent-api v0.25.5 kubedb.dev/apimachinery v0.36.0 kubedb.dev/db-client-go v0.0.8-0.20230818101900-6ddd035705ef + sigs.k8s.io/controller-runtime v0.13.1 sigs.k8s.io/yaml v1.3.0 stash.appscode.dev/apimachinery v0.32.0 ) @@ -139,7 +140,6 @@ require ( kmodules.xyz/offshoot-api v0.25.4 // indirect kmodules.xyz/prober v0.25.0 // indirect kubeops.dev/sidekick v0.0.2-0.20230113102427-9848f83b2f0f // indirect - sigs.k8s.io/controller-runtime v0.13.1 // indirect sigs.k8s.io/gateway-api v0.4.3 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect diff --git a/pkg/cmds/debug.go b/pkg/cmds/debug.go new file mode 100644 index 000000000..83b4d1435 --- /dev/null +++ b/pkg/cmds/debug.go @@ -0,0 +1,61 @@ +/* +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/debug" + + "github.com/spf13/cobra" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + debugLong = templates.LongDesc(` + Collect all the logs and yamls of a specific database in just one command + `) + debugExample = templates.Examples(` + kubectl dba debug mongodb -n demo sample-mongodb --operator-namespace kubedb + + Valid resource types include: + * elasticsearch + * mongodb + * mariadb + * mysql + * postgres + * redis +`) +) + +func NewCmdDebug(f cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "debug", + Short: i18n.T("Debug any Database issue"), + Long: debugLong, + Example: debugExample, + Run: func(cmd *cobra.Command, args []string) {}, + DisableFlagsInUseLine: true, + DisableAutoGenTag: true, + } + + cmd.AddCommand(debug.ElasticsearchDebugCMD(f)) + cmd.AddCommand(debug.MariaDBDebugCMD(f)) + cmd.AddCommand(debug.MongoDBDebugCMD(f)) + cmd.AddCommand(debug.MySQLDebugCMD(f)) + cmd.AddCommand(debug.PostgresDebugCMD(f)) + cmd.AddCommand(debug.RedisDebugCMD(f)) + + return cmd +} diff --git a/pkg/cmds/root.go b/pkg/cmds/root.go index f2f6ec103..c0cdc07e3 100644 --- a/pkg/cmds/root.go +++ b/pkg/cmds/root.go @@ -95,6 +95,12 @@ func NewKubeDBCommand(in io.Reader, out, err io.Writer) *cobra.Command { NewCmdData(f), }, }, + { + Message: "Debug any Database issue", + Commands: []*cobra.Command{ + NewCmdDebug(f), + }, + }, { Message: "Generate appbinding and secrets for remote Replica", Commands: []*cobra.Command{ diff --git a/pkg/debug/const.go b/pkg/debug/const.go new file mode 100644 index 000000000..424574ec2 --- /dev/null +++ b/pkg/debug/const.go @@ -0,0 +1,26 @@ +/* +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 debug + +const ( + logsDir = "logs" + yamlsDir = "yamls" + dirPerm = 0o755 + filePerm = 0o644 + + operatorContainerName = "operator" +) diff --git a/pkg/debug/elasticsearch.go b/pkg/debug/elasticsearch.go new file mode 100644 index 000000000..a2021a28b --- /dev/null +++ b/pkg/debug/elasticsearch.go @@ -0,0 +1,256 @@ +/* +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 debug + +import ( + "bytes" + "context" + "log" + "os" + "path" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + cs "kubedb.dev/apimachinery/client/clientset/versioned" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type elasticsearchOpts struct { + db *api.Elasticsearch + dbClient *cs.Clientset + podClient *kubernetes.Clientset + + operatorNamespace string + dir string + errWriter *bytes.Buffer +} + +func ElasticsearchDebugCMD(f cmdutil.Factory) *cobra.Command { + var ( + dbName string + operatorNamespace string + ) + + esDebugCmd := &cobra.Command{ + Use: "elasticsearch", + Aliases: []string{ + "es", + }, + Short: "Debug helper for elasticsearch database", + Example: `kubectl dba debug elasticsearch -n demo sample-elasticsearch --operator-namespace kubedb`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + log.Fatal("Enter elasticsearch object's name as an argument") + } + dbName = args[0] + + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + opts, err := newElasticsearchOpts(f, dbName, namespace, operatorNamespace) + if err != nil { + log.Fatalln(err) + } + + err = opts.collectOperatorLogs() + if err != nil { + log.Fatal(err) + } + + err = opts.collectForAllDBPods() + if err != nil { + log.Fatal(err) + } + + err = opts.collectOtherYamls() + if err != nil { + log.Fatal(err) + } + }, + } + esDebugCmd.Flags().StringVarP(&operatorNamespace, "operator-namespace", "o", "kubedb", "the namespace where the kubedb operator is installed") + + return esDebugCmd +} + +func newElasticsearchOpts(f cmdutil.Factory, dbName, namespace, operatorNS string) (*elasticsearchOpts, error) { + config, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + dbClient, err := cs.NewForConfig(config) + if err != nil { + return nil, err + } + + podClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + db, err := dbClient.KubedbV1alpha2().Elasticsearches(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pwd, _ := os.Getwd() + dir := path.Join(pwd, db.Name) + err = os.MkdirAll(path.Join(dir, logsDir), dirPerm) + if err != nil { + return nil, err + } + err = os.MkdirAll(path.Join(dir, yamlsDir), dirPerm) + if err != nil { + return nil, err + } + + opts := &elasticsearchOpts{ + db: db, + dbClient: dbClient, + podClient: podClient, + operatorNamespace: operatorNS, + dir: dir, + errWriter: &bytes.Buffer{}, + } + return opts, writeYaml(db, path.Join(opts.dir, yamlsDir)) +} + +func (opts *elasticsearchOpts) collectOperatorLogs() error { + pods, err := opts.podClient.CoreV1().Pods(opts.operatorNamespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + for _, pod := range pods.Items { + err = opts.writeLogs(pod.Name, pod.Namespace, operatorContainerName) + if err != nil { + return err + } + } + return nil +} + +func (opts *elasticsearchOpts) collectForAllDBPods() error { + dbLabels := labels.SelectorFromSet(opts.db.GetLabels()).String() + pods, err := opts.podClient.CoreV1().Pods(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: dbLabels, + }) + if err != nil { + return err + } + + podYamlDir := path.Join(opts.dir, yamlsDir) + for _, pod := range pods.Items { + err = opts.writeLogsForSinglePod(pod) + if err != nil { + return err + } + + err = writeYaml(&pod, podYamlDir) + if err != nil { + return err + } + + } + return nil +} + +func (opts *elasticsearchOpts) writeLogsForSinglePod(pod corev1.Pod) error { + for _, c := range pod.Spec.Containers { + err := opts.writeLogs(pod.Name, pod.Namespace, c.Name) + if err != nil { + return err + } + } + return nil +} + +func (opts *elasticsearchOpts) writeLogs(podName, ns, container string) error { + req := opts.podClient.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{ + Container: container, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := os.Create(path.Join(opts.dir, logsDir, podName+"_"+container+".log")) + if err != nil { + return err + } + defer logFile.Close() + + buf := make([]byte, 1024) + for { + bytesRead, err := podLogs.Read(buf) + if err != nil { + break + } + _, _ = logFile.Write(buf[:bytesRead]) + } + return nil +} + +func (opts *elasticsearchOpts) collectOtherYamls() error { + opsReqs, err := opts.dbClient.OpsV1alpha1().ElasticsearchOpsRequests(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + opsYamlDir := path.Join(opts.dir, yamlsDir, "ops") + err = os.MkdirAll(opsYamlDir, dirPerm) + if err != nil { + return err + } + for _, ops := range opsReqs.Items { + if ops.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&ops, opsYamlDir) + if err != nil { + return err + } + } + } + + scalers, err := opts.dbClient.AutoscalingV1alpha1().ElasticsearchAutoscalers(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + scalerYamlDir := path.Join(opts.dir, yamlsDir, "scaler") + err = os.MkdirAll(scalerYamlDir, dirPerm) + if err != nil { + return err + } + for _, sc := range scalers.Items { + if sc.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&sc, scalerYamlDir) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/debug/helpers.go b/pkg/debug/helpers.go new file mode 100644 index 000000000..57e2ec9d4 --- /dev/null +++ b/pkg/debug/helpers.go @@ -0,0 +1,33 @@ +/* +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 debug + +import ( + "os" + "path" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +func writeYaml(obj client.Object, fullPath string) error { + b, err := yaml.Marshal(obj) + if err != nil { + return err + } + return os.WriteFile(path.Join(fullPath, obj.GetName()+".yaml"), b, filePerm) +} diff --git a/pkg/debug/mariadb.go b/pkg/debug/mariadb.go new file mode 100644 index 000000000..fd8f520ef --- /dev/null +++ b/pkg/debug/mariadb.go @@ -0,0 +1,256 @@ +/* +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 debug + +import ( + "bytes" + "context" + "log" + "os" + "path" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + cs "kubedb.dev/apimachinery/client/clientset/versioned" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type mariadbOpts struct { + db *api.MariaDB + dbClient *cs.Clientset + podClient *kubernetes.Clientset + + operatorNamespace string + dir string + errWriter *bytes.Buffer +} + +func MariaDBDebugCMD(f cmdutil.Factory) *cobra.Command { + var ( + dbName string + operatorNamespace string + ) + + mdDebugCmd := &cobra.Command{ + Use: "mariadb", + Aliases: []string{ + "md", + }, + Short: "Debug helper for mariadb database", + Example: `kubectl dba debug mariadb -n demo sample-mariadb --operator-namespace kubedb`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + log.Fatal("Enter mariadb object's name as an argument") + } + dbName = args[0] + + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + opts, err := newMariadbOpts(f, dbName, namespace, operatorNamespace) + if err != nil { + log.Fatalln(err) + } + + err = opts.collectOperatorLogs() + if err != nil { + log.Fatal(err) + } + + err = opts.collectForAllDBPods() + if err != nil { + log.Fatal(err) + } + + err = opts.collectOtherYamls() + if err != nil { + log.Fatal(err) + } + }, + } + mdDebugCmd.Flags().StringVarP(&operatorNamespace, "operator-namespace", "o", "kubedb", "the namespace where the kubedb operator is installed") + + return mdDebugCmd +} + +func newMariadbOpts(f cmdutil.Factory, dbName, namespace, operatorNS string) (*mariadbOpts, error) { + config, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + dbClient, err := cs.NewForConfig(config) + if err != nil { + return nil, err + } + + podClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + db, err := dbClient.KubedbV1alpha2().MariaDBs(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pwd, _ := os.Getwd() + dir := path.Join(pwd, db.Name) + err = os.MkdirAll(path.Join(dir, logsDir), dirPerm) + if err != nil { + return nil, err + } + err = os.MkdirAll(path.Join(dir, yamlsDir), dirPerm) + if err != nil { + return nil, err + } + + opts := &mariadbOpts{ + db: db, + dbClient: dbClient, + podClient: podClient, + operatorNamespace: operatorNS, + dir: dir, + errWriter: &bytes.Buffer{}, + } + return opts, writeYaml(db, path.Join(opts.dir, yamlsDir)) +} + +func (opts *mariadbOpts) collectOperatorLogs() error { + pods, err := opts.podClient.CoreV1().Pods(opts.operatorNamespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + for _, pod := range pods.Items { + err = opts.writeLogs(pod.Name, pod.Namespace, operatorContainerName) + if err != nil { + return err + } + } + return nil +} + +func (opts *mariadbOpts) collectForAllDBPods() error { + dbLabels := labels.SelectorFromSet(opts.db.GetLabels()).String() + pods, err := opts.podClient.CoreV1().Pods(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: dbLabels, + }) + if err != nil { + return err + } + + podYamlDir := path.Join(opts.dir, yamlsDir) + for _, pod := range pods.Items { + err = opts.writeLogsForSinglePod(pod) + if err != nil { + return err + } + + err = writeYaml(&pod, podYamlDir) + if err != nil { + return err + } + + } + return nil +} + +func (opts *mariadbOpts) writeLogsForSinglePod(pod corev1.Pod) error { + for _, c := range pod.Spec.Containers { + err := opts.writeLogs(pod.Name, pod.Namespace, c.Name) + if err != nil { + return err + } + } + return nil +} + +func (opts *mariadbOpts) writeLogs(podName, ns, container string) error { + req := opts.podClient.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{ + Container: container, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := os.Create(path.Join(opts.dir, logsDir, podName+"_"+container+".log")) + if err != nil { + return err + } + defer logFile.Close() + + buf := make([]byte, 1024) + for { + bytesRead, err := podLogs.Read(buf) + if err != nil { + break + } + _, _ = logFile.Write(buf[:bytesRead]) + } + return nil +} + +func (opts *mariadbOpts) collectOtherYamls() error { + opsReqs, err := opts.dbClient.OpsV1alpha1().MariaDBOpsRequests(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + opsYamlDir := path.Join(opts.dir, yamlsDir, "ops") + err = os.MkdirAll(opsYamlDir, dirPerm) + if err != nil { + return err + } + for _, ops := range opsReqs.Items { + if ops.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&ops, opsYamlDir) + if err != nil { + return err + } + } + } + + scalers, err := opts.dbClient.AutoscalingV1alpha1().MariaDBAutoscalers(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + scalerYamlDir := path.Join(opts.dir, yamlsDir, "scaler") + err = os.MkdirAll(scalerYamlDir, dirPerm) + if err != nil { + return err + } + for _, sc := range scalers.Items { + if sc.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&sc, scalerYamlDir) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/debug/mongodb.go b/pkg/debug/mongodb.go new file mode 100644 index 000000000..c84244bd9 --- /dev/null +++ b/pkg/debug/mongodb.go @@ -0,0 +1,256 @@ +/* +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 debug + +import ( + "bytes" + "context" + "log" + "os" + "path" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + cs "kubedb.dev/apimachinery/client/clientset/versioned" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type mongodbOpts struct { + db *api.MongoDB + dbClient *cs.Clientset + podClient *kubernetes.Clientset + + operatorNamespace string + dir string + errWriter *bytes.Buffer +} + +func MongoDBDebugCMD(f cmdutil.Factory) *cobra.Command { + var ( + dbName string + operatorNamespace string + ) + + mgDebugCmd := &cobra.Command{ + Use: "mongodb", + Aliases: []string{ + "mg", + }, + Short: "Debug helper for mongodb database", + Example: `kubectl dba debug mongodb -n demo sample-mongodb --operator-namespace kubedb`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + log.Fatal("Enter mongodb object's name as an argument") + } + dbName = args[0] + + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + opts, err := newMongodbOpts(f, dbName, namespace, operatorNamespace) + if err != nil { + log.Fatalln(err) + } + + err = opts.collectOperatorLogs() + if err != nil { + log.Fatal(err) + } + + err = opts.collectForAllDBPods() + if err != nil { + log.Fatal(err) + } + + err = opts.collectOtherYamls() + if err != nil { + log.Fatal(err) + } + }, + } + mgDebugCmd.Flags().StringVarP(&operatorNamespace, "operator-namespace", "o", "kubedb", "the namespace where the kubedb operator is installed") + + return mgDebugCmd +} + +func newMongodbOpts(f cmdutil.Factory, dbName, namespace, operatorNS string) (*mongodbOpts, error) { + config, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + dbClient, err := cs.NewForConfig(config) + if err != nil { + return nil, err + } + + podClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + db, err := dbClient.KubedbV1alpha2().MongoDBs(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pwd, _ := os.Getwd() + dir := path.Join(pwd, db.Name) + err = os.MkdirAll(path.Join(dir, logsDir), dirPerm) + if err != nil { + return nil, err + } + err = os.MkdirAll(path.Join(dir, yamlsDir), dirPerm) + if err != nil { + return nil, err + } + + opts := &mongodbOpts{ + db: db, + dbClient: dbClient, + podClient: podClient, + operatorNamespace: operatorNS, + dir: dir, + errWriter: &bytes.Buffer{}, + } + return opts, writeYaml(db, path.Join(opts.dir, yamlsDir)) +} + +func (opts *mongodbOpts) collectOperatorLogs() error { + pods, err := opts.podClient.CoreV1().Pods(opts.operatorNamespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + for _, pod := range pods.Items { + err = opts.writeLogs(pod.Name, pod.Namespace, operatorContainerName) + if err != nil { + return err + } + } + return nil +} + +func (opts *mongodbOpts) collectForAllDBPods() error { + dbLabels := labels.SelectorFromSet(opts.db.GetLabels()).String() + pods, err := opts.podClient.CoreV1().Pods(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: dbLabels, + }) + if err != nil { + return err + } + + podYamlDir := path.Join(opts.dir, yamlsDir) + for _, pod := range pods.Items { + err = opts.writeLogsForSinglePod(pod) + if err != nil { + return err + } + + err = writeYaml(&pod, podYamlDir) + if err != nil { + return err + } + + } + return nil +} + +func (opts *mongodbOpts) writeLogsForSinglePod(pod corev1.Pod) error { + for _, c := range pod.Spec.Containers { + err := opts.writeLogs(pod.Name, pod.Namespace, c.Name) + if err != nil { + return err + } + } + return nil +} + +func (opts *mongodbOpts) writeLogs(podName, ns, container string) error { + req := opts.podClient.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{ + Container: container, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := os.Create(path.Join(opts.dir, logsDir, podName+"_"+container+".log")) + if err != nil { + return err + } + defer logFile.Close() + + buf := make([]byte, 1024) + for { + bytesRead, err := podLogs.Read(buf) + if err != nil { + break + } + _, _ = logFile.Write(buf[:bytesRead]) + } + return nil +} + +func (opts *mongodbOpts) collectOtherYamls() error { + opsReqs, err := opts.dbClient.OpsV1alpha1().MongoDBOpsRequests(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + opsYamlDir := path.Join(opts.dir, yamlsDir, "ops") + err = os.MkdirAll(opsYamlDir, dirPerm) + if err != nil { + return err + } + for _, ops := range opsReqs.Items { + if ops.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&ops, opsYamlDir) + if err != nil { + return err + } + } + } + + scalers, err := opts.dbClient.AutoscalingV1alpha1().MongoDBAutoscalers(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + scalerYamlDir := path.Join(opts.dir, yamlsDir, "scaler") + err = os.MkdirAll(scalerYamlDir, dirPerm) + if err != nil { + return err + } + for _, sc := range scalers.Items { + if sc.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&sc, scalerYamlDir) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/debug/mysql.go b/pkg/debug/mysql.go new file mode 100644 index 000000000..6b090784d --- /dev/null +++ b/pkg/debug/mysql.go @@ -0,0 +1,256 @@ +/* +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 debug + +import ( + "bytes" + "context" + "log" + "os" + "path" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + cs "kubedb.dev/apimachinery/client/clientset/versioned" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type mysqlOpts struct { + db *api.MySQL + dbClient *cs.Clientset + podClient *kubernetes.Clientset + + operatorNamespace string + dir string + errWriter *bytes.Buffer +} + +func MySQLDebugCMD(f cmdutil.Factory) *cobra.Command { + var ( + dbName string + operatorNamespace string + ) + + myDebugCmd := &cobra.Command{ + Use: "mysql", + Aliases: []string{ + "my", + }, + Short: "Debug helper for mysql database", + Example: `kubectl dba debug mysql -n demo sample-mysql --operator-namespace kubedb`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + log.Fatal("Enter mysql object's name as an argument") + } + dbName = args[0] + + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + opts, err := newMysqlOpts(f, dbName, namespace, operatorNamespace) + if err != nil { + log.Fatalln(err) + } + + err = opts.collectOperatorLogs() + if err != nil { + log.Fatal(err) + } + + err = opts.collectForAllDBPods() + if err != nil { + log.Fatal(err) + } + + err = opts.collectOtherYamls() + if err != nil { + log.Fatal(err) + } + }, + } + myDebugCmd.Flags().StringVarP(&operatorNamespace, "operator-namespace", "o", "kubedb", "the namespace where the kubedb operator is installed") + + return myDebugCmd +} + +func newMysqlOpts(f cmdutil.Factory, dbName, namespace, operatorNS string) (*mysqlOpts, error) { + config, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + dbClient, err := cs.NewForConfig(config) + if err != nil { + return nil, err + } + + podClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + db, err := dbClient.KubedbV1alpha2().MySQLs(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pwd, _ := os.Getwd() + dir := path.Join(pwd, db.Name) + err = os.MkdirAll(path.Join(dir, logsDir), dirPerm) + if err != nil { + return nil, err + } + err = os.MkdirAll(path.Join(dir, yamlsDir), dirPerm) + if err != nil { + return nil, err + } + + opts := &mysqlOpts{ + db: db, + dbClient: dbClient, + podClient: podClient, + operatorNamespace: operatorNS, + dir: dir, + errWriter: &bytes.Buffer{}, + } + return opts, writeYaml(db, path.Join(opts.dir, yamlsDir)) +} + +func (opts *mysqlOpts) collectOperatorLogs() error { + pods, err := opts.podClient.CoreV1().Pods(opts.operatorNamespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + for _, pod := range pods.Items { + err = opts.writeLogs(pod.Name, pod.Namespace, operatorContainerName) + if err != nil { + return err + } + } + return nil +} + +func (opts *mysqlOpts) collectForAllDBPods() error { + dbLabels := labels.SelectorFromSet(opts.db.GetLabels()).String() + pods, err := opts.podClient.CoreV1().Pods(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: dbLabels, + }) + if err != nil { + return err + } + + podYamlDir := path.Join(opts.dir, yamlsDir) + for _, pod := range pods.Items { + err = opts.writeLogsForSinglePod(pod) + if err != nil { + return err + } + + err = writeYaml(&pod, podYamlDir) + if err != nil { + return err + } + + } + return nil +} + +func (opts *mysqlOpts) writeLogsForSinglePod(pod corev1.Pod) error { + for _, c := range pod.Spec.Containers { + err := opts.writeLogs(pod.Name, pod.Namespace, c.Name) + if err != nil { + return err + } + } + return nil +} + +func (opts *mysqlOpts) writeLogs(podName, ns, container string) error { + req := opts.podClient.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{ + Container: container, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := os.Create(path.Join(opts.dir, logsDir, podName+"_"+container+".log")) + if err != nil { + return err + } + defer logFile.Close() + + buf := make([]byte, 1024) + for { + bytesRead, err := podLogs.Read(buf) + if err != nil { + break + } + _, _ = logFile.Write(buf[:bytesRead]) + } + return nil +} + +func (opts *mysqlOpts) collectOtherYamls() error { + opsReqs, err := opts.dbClient.OpsV1alpha1().MySQLOpsRequests(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + opsYamlDir := path.Join(opts.dir, yamlsDir, "ops") + err = os.MkdirAll(opsYamlDir, dirPerm) + if err != nil { + return err + } + for _, ops := range opsReqs.Items { + if ops.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&ops, opsYamlDir) + if err != nil { + return err + } + } + } + + scalers, err := opts.dbClient.AutoscalingV1alpha1().MySQLAutoscalers(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + scalerYamlDir := path.Join(opts.dir, yamlsDir, "scaler") + err = os.MkdirAll(scalerYamlDir, dirPerm) + if err != nil { + return err + } + for _, sc := range scalers.Items { + if sc.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&sc, scalerYamlDir) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/debug/postgres.go b/pkg/debug/postgres.go new file mode 100644 index 000000000..e78ffb45b --- /dev/null +++ b/pkg/debug/postgres.go @@ -0,0 +1,256 @@ +/* +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 debug + +import ( + "bytes" + "context" + "log" + "os" + "path" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + cs "kubedb.dev/apimachinery/client/clientset/versioned" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type postgresOpts struct { + db *api.Postgres + dbClient *cs.Clientset + podClient *kubernetes.Clientset + + operatorNamespace string + dir string + errWriter *bytes.Buffer +} + +func PostgresDebugCMD(f cmdutil.Factory) *cobra.Command { + var ( + dbName string + operatorNamespace string + ) + + pgDebugCmd := &cobra.Command{ + Use: "postgres", + Aliases: []string{ + "pg", + }, + Short: "Debug helper for postgres database", + Example: `kubectl dba debug postgres -n demo sample-postgres --operator-namespace kubedb`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + log.Fatal("Enter postgres object's name as an argument") + } + dbName = args[0] + + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + opts, err := newPostgresOpts(f, dbName, namespace, operatorNamespace) + if err != nil { + log.Fatalln(err) + } + + err = opts.collectOperatorLogs() + if err != nil { + log.Fatal(err) + } + + err = opts.collectForAllDBPods() + if err != nil { + log.Fatal(err) + } + + err = opts.collectOtherYamls() + if err != nil { + log.Fatal(err) + } + }, + } + pgDebugCmd.Flags().StringVarP(&operatorNamespace, "operator-namespace", "o", "kubedb", "the namespace where the kubedb operator is installed") + + return pgDebugCmd +} + +func newPostgresOpts(f cmdutil.Factory, dbName, namespace, operatorNS string) (*postgresOpts, error) { + config, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + dbClient, err := cs.NewForConfig(config) + if err != nil { + return nil, err + } + + podClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + db, err := dbClient.KubedbV1alpha2().Postgreses(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pwd, _ := os.Getwd() + dir := path.Join(pwd, db.Name) + err = os.MkdirAll(path.Join(dir, logsDir), dirPerm) + if err != nil { + return nil, err + } + err = os.MkdirAll(path.Join(dir, yamlsDir), dirPerm) + if err != nil { + return nil, err + } + + opts := &postgresOpts{ + db: db, + dbClient: dbClient, + podClient: podClient, + operatorNamespace: operatorNS, + dir: dir, + errWriter: &bytes.Buffer{}, + } + return opts, writeYaml(db, path.Join(opts.dir, yamlsDir)) +} + +func (opts *postgresOpts) collectOperatorLogs() error { + pods, err := opts.podClient.CoreV1().Pods(opts.operatorNamespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + for _, pod := range pods.Items { + err = opts.writeLogs(pod.Name, pod.Namespace, operatorContainerName) + if err != nil { + return err + } + } + return nil +} + +func (opts *postgresOpts) collectForAllDBPods() error { + dbLabels := labels.SelectorFromSet(opts.db.GetLabels()).String() + pods, err := opts.podClient.CoreV1().Pods(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: dbLabels, + }) + if err != nil { + return err + } + + podYamlDir := path.Join(opts.dir, yamlsDir) + for _, pod := range pods.Items { + err = opts.writeLogsForSinglePod(pod) + if err != nil { + return err + } + + err = writeYaml(&pod, podYamlDir) + if err != nil { + return err + } + + } + return nil +} + +func (opts *postgresOpts) writeLogsForSinglePod(pod corev1.Pod) error { + for _, c := range pod.Spec.Containers { + err := opts.writeLogs(pod.Name, pod.Namespace, c.Name) + if err != nil { + return err + } + } + return nil +} + +func (opts *postgresOpts) writeLogs(podName, ns, container string) error { + req := opts.podClient.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{ + Container: container, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := os.Create(path.Join(opts.dir, logsDir, podName+"_"+container+".log")) + if err != nil { + return err + } + defer logFile.Close() + + buf := make([]byte, 1024) + for { + bytesRead, err := podLogs.Read(buf) + if err != nil { + break + } + _, _ = logFile.Write(buf[:bytesRead]) + } + return nil +} + +func (opts *postgresOpts) collectOtherYamls() error { + opsReqs, err := opts.dbClient.OpsV1alpha1().PostgresOpsRequests(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + opsYamlDir := path.Join(opts.dir, yamlsDir, "ops") + err = os.MkdirAll(opsYamlDir, dirPerm) + if err != nil { + return err + } + for _, ops := range opsReqs.Items { + if ops.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&ops, opsYamlDir) + if err != nil { + return err + } + } + } + + scalers, err := opts.dbClient.AutoscalingV1alpha1().PostgresAutoscalers(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + scalerYamlDir := path.Join(opts.dir, yamlsDir, "scaler") + err = os.MkdirAll(scalerYamlDir, dirPerm) + if err != nil { + return err + } + for _, sc := range scalers.Items { + if sc.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&sc, scalerYamlDir) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/debug/redis.go b/pkg/debug/redis.go new file mode 100644 index 000000000..b79cc55bb --- /dev/null +++ b/pkg/debug/redis.go @@ -0,0 +1,256 @@ +/* +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 debug + +import ( + "bytes" + "context" + "log" + "os" + "path" + + api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" + cs "kubedb.dev/apimachinery/client/clientset/versioned" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +type redisOpts struct { + db *api.Redis + dbClient *cs.Clientset + podClient *kubernetes.Clientset + + operatorNamespace string + dir string + errWriter *bytes.Buffer +} + +func RedisDebugCMD(f cmdutil.Factory) *cobra.Command { + var ( + dbName string + operatorNamespace string + ) + + rdDebugCmd := &cobra.Command{ + Use: "redis", + Aliases: []string{ + "rd", + }, + Short: "Debug helper for redis database", + Example: `kubectl dba debug redis -n demo sample-redis --operator-namespace kubedb`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + log.Fatal("Enter redis object's name as an argument") + } + dbName = args[0] + + namespace, _, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + klog.Error(err, "failed to get current namespace") + } + + opts, err := newRedisOpts(f, dbName, namespace, operatorNamespace) + if err != nil { + log.Fatalln(err) + } + + err = opts.collectOperatorLogs() + if err != nil { + log.Fatal(err) + } + + err = opts.collectForAllDBPods() + if err != nil { + log.Fatal(err) + } + + err = opts.collectOtherYamls() + if err != nil { + log.Fatal(err) + } + }, + } + rdDebugCmd.Flags().StringVarP(&operatorNamespace, "operator-namespace", "o", "kubedb", "the namespace where the kubedb operator is installed") + + return rdDebugCmd +} + +func newRedisOpts(f cmdutil.Factory, dbName, namespace, operatorNS string) (*redisOpts, error) { + config, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + + dbClient, err := cs.NewForConfig(config) + if err != nil { + return nil, err + } + + podClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + db, err := dbClient.KubedbV1alpha2().Redises(namespace).Get(context.TODO(), dbName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pwd, _ := os.Getwd() + dir := path.Join(pwd, db.Name) + err = os.MkdirAll(path.Join(dir, logsDir), dirPerm) + if err != nil { + return nil, err + } + err = os.MkdirAll(path.Join(dir, yamlsDir), dirPerm) + if err != nil { + return nil, err + } + + opts := &redisOpts{ + db: db, + dbClient: dbClient, + podClient: podClient, + operatorNamespace: operatorNS, + dir: dir, + errWriter: &bytes.Buffer{}, + } + return opts, writeYaml(db, path.Join(opts.dir, yamlsDir)) +} + +func (opts *redisOpts) collectOperatorLogs() error { + pods, err := opts.podClient.CoreV1().Pods(opts.operatorNamespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + for _, pod := range pods.Items { + err = opts.writeLogs(pod.Name, pod.Namespace, operatorContainerName) + if err != nil { + return err + } + } + return nil +} + +func (opts *redisOpts) collectForAllDBPods() error { + dbLabels := labels.SelectorFromSet(opts.db.GetLabels()).String() + pods, err := opts.podClient.CoreV1().Pods(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: dbLabels, + }) + if err != nil { + return err + } + + podYamlDir := path.Join(opts.dir, yamlsDir) + for _, pod := range pods.Items { + err = opts.writeLogsForSinglePod(pod) + if err != nil { + return err + } + + err = writeYaml(&pod, podYamlDir) + if err != nil { + return err + } + + } + return nil +} + +func (opts *redisOpts) writeLogsForSinglePod(pod corev1.Pod) error { + for _, c := range pod.Spec.Containers { + err := opts.writeLogs(pod.Name, pod.Namespace, c.Name) + if err != nil { + return err + } + } + return nil +} + +func (opts *redisOpts) writeLogs(podName, ns, container string) error { + req := opts.podClient.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{ + Container: container, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := os.Create(path.Join(opts.dir, logsDir, podName+"_"+container+".log")) + if err != nil { + return err + } + defer logFile.Close() + + buf := make([]byte, 1024) + for { + bytesRead, err := podLogs.Read(buf) + if err != nil { + break + } + _, _ = logFile.Write(buf[:bytesRead]) + } + return nil +} + +func (opts *redisOpts) collectOtherYamls() error { + opsReqs, err := opts.dbClient.OpsV1alpha1().RedisOpsRequests(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + opsYamlDir := path.Join(opts.dir, yamlsDir, "ops") + err = os.MkdirAll(opsYamlDir, dirPerm) + if err != nil { + return err + } + for _, ops := range opsReqs.Items { + if ops.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&ops, opsYamlDir) + if err != nil { + return err + } + } + } + + scalers, err := opts.dbClient.AutoscalingV1alpha1().RedisAutoscalers(opts.db.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + scalerYamlDir := path.Join(opts.dir, yamlsDir, "scaler") + err = os.MkdirAll(scalerYamlDir, dirPerm) + if err != nil { + return err + } + for _, sc := range scalers.Items { + if sc.Spec.DatabaseRef.Name == opts.db.Name { + err = writeYaml(&sc, scalerYamlDir) + if err != nil { + return err + } + } + } + return nil +}