github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/list_logs.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package cluster 21 22 import ( 23 "bytes" 24 "fmt" 25 "os" 26 "strings" 27 28 "github.com/spf13/cobra" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/cli-runtime/pkg/genericiooptions" 31 "k8s.io/client-go/dynamic" 32 "k8s.io/client-go/kubernetes" 33 cmdutil "k8s.io/kubectl/pkg/cmd/util" 34 "k8s.io/kubectl/pkg/util/templates" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 "github.com/1aal/kubeblocks/pkg/cli/cluster" 38 "github.com/1aal/kubeblocks/pkg/cli/exec" 39 "github.com/1aal/kubeblocks/pkg/cli/printer" 40 "github.com/1aal/kubeblocks/pkg/cli/types" 41 "github.com/1aal/kubeblocks/pkg/cli/util" 42 "github.com/1aal/kubeblocks/pkg/constant" 43 ) 44 45 var ( 46 logsListExample = templates.Examples(` 47 # Display supported log files in cluster mycluster with all instance 48 kbcli cluster list-logs mycluster 49 50 # Display supported log files in cluster mycluster with specify component my-component 51 kbcli cluster list-logs mycluster --component my-component 52 53 # Display supported log files in cluster mycluster with specify instance my-instance-0 54 kbcli cluster list-logs mycluster --instance my-instance-0`) 55 ) 56 57 // ListLogsOptions declares the arguments accepted by the list-logs command 58 type ListLogsOptions struct { 59 namespace string 60 clusterName string 61 componentName string 62 instName string 63 64 dynamicClient dynamic.Interface 65 clientSet *kubernetes.Clientset 66 factory cmdutil.Factory 67 genericiooptions.IOStreams 68 exec *exec.ExecOptions 69 } 70 71 func NewListLogsCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 72 o := &ListLogsOptions{ 73 factory: f, 74 IOStreams: streams, 75 } 76 77 cmd := &cobra.Command{ 78 Use: "list-logs NAME", 79 Short: "List supported log files in cluster.", 80 Example: logsListExample, 81 ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()), 82 Run: func(cmd *cobra.Command, args []string) { 83 util.CheckErr(o.Validate(args)) 84 util.CheckErr(o.Complete(f, args)) 85 util.CheckErr(o.Run()) 86 }, 87 } 88 cmd.Flags().StringVarP(&o.instName, "instance", "i", "", "Instance name.") 89 cmd.Flags().StringVar(&o.componentName, "component", "", "Component name.") 90 return cmd 91 } 92 93 func (o *ListLogsOptions) Validate(args []string) error { 94 if len(args) < 1 { 95 return fmt.Errorf("must specify the cluster name") 96 } 97 return nil 98 } 99 100 func (o *ListLogsOptions) Complete(f cmdutil.Factory, args []string) error { 101 // set cluster name from args 102 o.clusterName = args[0] 103 config, err := o.factory.ToRESTConfig() 104 if err != nil { 105 return err 106 } 107 o.namespace, _, err = f.ToRawKubeConfigLoader().Namespace() 108 if err != nil { 109 return err 110 } 111 o.clientSet, err = o.factory.KubernetesClientSet() 112 if err != nil { 113 return err 114 } 115 o.dynamicClient, err = f.DynamicClient() 116 o.exec = exec.NewExecOptions(o.factory, o.IOStreams) 117 o.exec.Config = config 118 // hide unnecessary output 119 o.exec.Quiet = true 120 return err 121 } 122 123 func (o *ListLogsOptions) Run() error { 124 clusterGetter := cluster.ObjectsGetter{ 125 Client: o.clientSet, 126 Dynamic: o.dynamicClient, 127 Name: o.clusterName, 128 Namespace: o.namespace, 129 GetOptions: cluster.GetOptions{ 130 WithClusterDef: true, 131 WithPod: true, 132 }, 133 } 134 dataObj, err := clusterGetter.Get() 135 if err != nil { 136 return err 137 } 138 if err := o.printListLogs(dataObj); err != nil { 139 return err 140 } 141 return nil 142 } 143 144 // printListLogs prints the result of list-logs command to stdout. 145 func (o *ListLogsOptions) printListLogs(dataObj *cluster.ClusterObjects) error { 146 tbl := printer.NewTablePrinter(o.Out) 147 logFilesData := o.gatherLogFilesData(dataObj.Cluster, dataObj.ClusterDef, dataObj.Pods) 148 if len(logFilesData) == 0 { 149 fmt.Fprintf(o.ErrOut, "No log files found. You can enable the log feature with the kbcli command below.\n"+ 150 "kbcli cluster update %s --enable-all-logs=true --namespace %s\n", dataObj.Cluster.Name, dataObj.Cluster.Namespace) 151 } else { 152 tbl.SetHeader("INSTANCE", "LOG-TYPE", "FILE-PATH", "SIZE", "LAST-WRITTEN", "COMPONENT") 153 for _, f := range logFilesData { 154 tbl.AddRow(f.instance, f.logType, f.filePath, f.size, f.lastWritten, f.component) 155 } 156 tbl.Print() 157 } 158 return nil 159 } 160 161 type logFileInfo struct { 162 instance string 163 logType string 164 filePath string 165 size string 166 lastWritten string 167 component string 168 } 169 170 // gatherLogFilesData gathers all log files data from each instance of the cluster. 171 func (o *ListLogsOptions) gatherLogFilesData(c *appsv1alpha1.Cluster, cd *appsv1alpha1.ClusterDefinition, pods *corev1.PodList) []logFileInfo { 172 logFileInfoList := make([]logFileInfo, 0, len(pods.Items)) 173 for _, p := range pods.Items { 174 if len(o.instName) > 0 && !strings.EqualFold(p.Name, o.instName) { 175 continue 176 } 177 componentName, ok := p.Labels[constant.KBAppComponentLabelKey] 178 if !ok || (len(o.componentName) > 0 && !strings.EqualFold(o.componentName, componentName)) { 179 continue 180 } 181 var compDefName string 182 logTypeMap := make(map[string]struct{}) 183 // find component compDefName and enabledLogs config against componentName in pod's label. 184 for _, comCluster := range c.Spec.ComponentSpecs { 185 if !strings.EqualFold(comCluster.Name, componentName) { 186 continue 187 } 188 compDefName = comCluster.ComponentDefRef 189 for _, logType := range comCluster.EnabledLogs { 190 logTypeMap[logType] = struct{}{} 191 } 192 break 193 } 194 if len(compDefName) == 0 || len(logTypeMap) == 0 { 195 continue 196 } 197 for _, com := range cd.Spec.ComponentDefs { 198 if !strings.EqualFold(com.Name, compDefName) { 199 continue 200 } 201 for _, logConfig := range com.LogConfigs { 202 if _, ok := logTypeMap[logConfig.Name]; ok { 203 realFile, err := o.getRealFileFromContainer(&p, logConfig.FilePathPattern) 204 if err == nil { 205 logFileInfoList = append(logFileInfoList, convertToLogFileInfo(realFile, logConfig.Name, p.Name, componentName)...) 206 } 207 } 208 } 209 break 210 } 211 } 212 return logFileInfoList 213 } 214 215 // convertToLogFileInfo converts file info in string format to logFileInfo struct. 216 func convertToLogFileInfo(fileInfo, logType, instName, component string) []logFileInfo { 217 fileList := strings.Split(fileInfo, "\n") 218 logFileList := make([]logFileInfo, 0, len(fileList)) 219 for _, file := range fileList { 220 fieldList := strings.Fields(file) 221 if len(fieldList) == 0 { 222 continue 223 } 224 logFileList = append(logFileList, logFileInfo{ 225 instance: instName, 226 component: component, 227 logType: logType, 228 size: fieldList[4], 229 lastWritten: strings.Join(fieldList[5:10], " "), 230 filePath: fieldList[10], 231 }) 232 } 233 return logFileList 234 } 235 236 // getRealFileFromContainer gets real log files against pattern from container, and returns file info in string format 237 func (o *ListLogsOptions) getRealFileFromContainer(pod *corev1.Pod, pattern string) (string, error) { 238 o.exec.Pod = pod 239 // linux cmd : ls -lh --time-style='+%b %d, %Y %H:%M (UTC%:z)' pattern 240 o.exec.Command = []string{"/bin/bash", "-c", "ls -lh --time-style='+%b %d, %Y %H:%M (UTC%:z)' " + pattern} 241 // set customized output 242 out := bytes.Buffer{} 243 o.exec.Out = &out 244 o.exec.ErrOut = os.Stdout 245 o.exec.TTY = false 246 if err := o.exec.Run(); err != nil { 247 return out.String(), err 248 } 249 return out.String(), nil 250 }