github.com/fafucoder/cilium@v1.6.11/cilium/cmd/debuginfo.go (about) 1 // Copyright 2017-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cmd 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "regexp" 27 "strconv" 28 "strings" 29 "text/tabwriter" 30 "time" 31 32 "github.com/cilium/cilium/api/v1/models" 33 pkg "github.com/cilium/cilium/pkg/client" 34 "github.com/cilium/cilium/pkg/command" 35 36 "github.com/russross/blackfriday" 37 "github.com/spf13/cobra" 38 ) 39 40 // outputTypes enum definition 41 type outputType int 42 43 // outputTypes enum values 44 const ( 45 STDOUT outputType = 0 + iota 46 MARKDOWN 47 HTML 48 JSONOUTPUT 49 JSONPATH 50 ) 51 52 var ( 53 // Can't call it jsonOutput because another var in this package uses that. 54 jsonOutputDebuginfo = "json" 55 markdownOutput = "markdown" 56 htmlOutput = "html" 57 jsonpathOutput = "jsonpath" 58 jsonPathRegExp = regexp.MustCompile(`^jsonpath\=(.*)`) 59 ) 60 61 // outputTypes enum strings 62 var outputTypes = [...]string{ 63 "STDOUT", 64 markdownOutput, 65 htmlOutput, 66 jsonOutputDebuginfo, 67 jsonpathOutput, 68 } 69 70 var debuginfoCmd = &cobra.Command{ 71 Use: "debuginfo", 72 Short: "Request available debugging information from agent", 73 Run: runDebugInfo, 74 } 75 76 var ( 77 outputToFile bool 78 html string 79 filePerCommand bool 80 outputOpts []string 81 outputDir string 82 ) 83 84 type addSection func(*tabwriter.Writer, *models.DebugInfo) 85 86 var sections = map[string]addSection{ 87 "cilium-version": addCiliumVersion, 88 "kernel-version": addKernelVersion, 89 "cilium-status": addCiliumStatus, 90 "cilium-environment-keys": addCiliumEnvironmentKeys, 91 "cilium-endpoint-list": addCiliumEndpointList, 92 "cilium-service-list": addCiliumServiceList, 93 "cilium-policy": addCiliumPolicy, 94 "cilium-memory-map": addCiliumMemoryMap, 95 "cilium-subsystems": addSubsystems, 96 } 97 98 func init() { 99 rootCmd.AddCommand(debuginfoCmd) 100 debuginfoCmd.Flags().BoolVarP(&outputToFile, "file", "f", false, "Redirect output to file(s)") 101 debuginfoCmd.Flags().BoolVarP(&filePerCommand, "file-per-command", "", false, "Generate a single file per command") 102 debuginfoCmd.Flags().StringSliceVar(&outputOpts, "output", []string{}, "markdown| html| json| jsonpath='{}'") 103 debuginfoCmd.Flags().StringVar(&outputDir, "output-directory", "", "directory for files (if specified will use directory in which this command was ran)") 104 } 105 106 func validateInput() []outputType { 107 if outputDir != "" && !outputToFile { 108 fmt.Fprintf(os.Stderr, "invalid option combination; specified output-directory %q, but did not specify for output to be redirected to file; exiting\n", outputDir) 109 os.Exit(1) 110 } 111 return validateOutputOpts() 112 } 113 114 func validateOutputOpts() []outputType { 115 116 var outputTypes []outputType 117 for _, outputOpt := range outputOpts { 118 switch strings.ToLower(outputOpt) { 119 case markdownOutput: 120 outputTypes = append(outputTypes, MARKDOWN) 121 case htmlOutput: 122 if !outputToFile { 123 fmt.Fprintf(os.Stderr, "if HTML is specified as the output format, it is required that you provide the `--file` argument as well\n") 124 os.Exit(1) 125 } 126 outputTypes = append(outputTypes, HTML) 127 case jsonOutputDebuginfo: 128 if filePerCommand { 129 fmt.Fprintf(os.Stderr, "%s does not support dumping a file per command; exiting\n", outputOpt) 130 os.Exit(1) 131 } 132 outputTypes = append(outputTypes, JSONOUTPUT) 133 // Empty JSONPath filter case. 134 case jsonpathOutput: 135 if filePerCommand { 136 fmt.Fprintf(os.Stderr, "%s does not support dumping a file per command; exiting\n", outputOpt) 137 os.Exit(1) 138 } 139 outputTypes = append(outputTypes, JSONPATH) 140 default: 141 // Check to see if arg contains jsonpath filtering as well. 142 if jsonPathRegExp.MatchString(outputOpt) { 143 outputTypes = append(outputTypes, JSONPATH) 144 continue 145 } 146 fmt.Fprintf(os.Stderr, "%s is not a valid output format; exiting\n", outputOpt) 147 os.Exit(1) 148 } 149 } 150 return outputTypes 151 } 152 153 func formatFileName(outputDir string, cmdTime time.Time, outtype outputType) string { 154 var fileName string 155 var sep string 156 if outputDir != "" { 157 sep = outputDir + "/" 158 } 159 timeStr := cmdTime.Format("20060102-150405.999-0700-MST") 160 switch outtype { 161 case MARKDOWN: 162 fileName = fmt.Sprintf("%scilium-debuginfo-%s.md", sep, timeStr) 163 case HTML: 164 fileName = fmt.Sprintf("%scilium-debuginfo-%s.html", sep, timeStr) 165 case JSONOUTPUT: 166 fileName = fmt.Sprintf("%scilium-debuginfo-%s.json", sep, timeStr) 167 case JSONPATH: 168 fileName = fmt.Sprintf("%scilium-debuginfo-%s.jsonpath", sep, timeStr) 169 default: 170 fileName = fmt.Sprintf("%scilium-debuginfo-%s.md", sep, timeStr) 171 } 172 return fileName 173 } 174 175 func rootWarningMessage() { 176 fmt.Fprint(os.Stderr, "Warning, some of the BPF commands might fail when not run as root\n") 177 } 178 179 func runDebugInfo(cmd *cobra.Command, args []string) { 180 181 outputTypes := validateInput() 182 183 resp, err := client.Daemon.GetDebuginfo(nil) 184 if err != nil { 185 fmt.Fprintf(os.Stderr, "%s\n", pkg.Hint(err)) 186 os.Exit(1) 187 } 188 189 // create tab-writer to fill buffer 190 var buf bytes.Buffer 191 w := tabwriter.NewWriter(&buf, 5, 0, 3, ' ', 0) 192 p := resp.Payload 193 194 cmdTime := time.Now() 195 196 if outputToFile && len(outputTypes) == 0 { 197 outputTypes = append(outputTypes, MARKDOWN) 198 } 199 200 // Dump payload for each output format. 201 for i, output := range outputTypes { 202 var fileName string 203 204 // Only warn when not dumping output as JSON so that when the output of the 205 // command is specified to be JSON, the only outputted content is the JSON 206 // model of debuginfo. 207 if os.Getuid() != 0 && output != JSONOUTPUT && output != JSONPATH { 208 rootWarningMessage() 209 } 210 211 if outputToFile { 212 fileName = formatFileName(outputDir, cmdTime, output) 213 } 214 215 // Generate multiple files for each subsection of the command if 216 // specified, except in the JSON cases, because in the JSON cases, 217 // we want to dump the entire DebugInfo JSON object, not sections of it. 218 if filePerCommand && (output != JSONOUTPUT && output != JSONPATH) { 219 for cmdName, section := range sections { 220 addHeader(w) 221 section(w, p) 222 writeToOutput(buf, output, fileName, cmdName) 223 buf.Reset() 224 } 225 continue 226 } 227 228 // Generate a single file, except not for JSON; no formatting is 229 // needed. 230 if output == JSONOUTPUT || output == JSONPATH { 231 marshaledDebugInfo, _ := p.MarshalBinary() 232 buf.Write(marshaledDebugInfo) 233 if output == JSONOUTPUT { 234 writeToOutput(buf, output, fileName, "") 235 } else { 236 writeJSONPathToOutput(buf, fileName, "", outputOpts[i]) 237 } 238 buf.Reset() 239 } else { 240 addHeader(w) 241 for _, section := range sections { 242 section(w, p) 243 } 244 writeToOutput(buf, output, fileName, "") 245 buf.Reset() 246 } 247 } 248 249 if len(outputTypes) > 0 { 250 return 251 } 252 253 if os.Getuid() != 0 { 254 rootWarningMessage() 255 } 256 257 // Just write to stdout in markdown formats if no output option specified. 258 addHeader(w) 259 for _, section := range sections { 260 section(w, p) 261 } 262 writeToOutput(buf, STDOUT, "", "") 263 264 } 265 266 func addHeader(w *tabwriter.Writer) { 267 fmt.Fprintf(w, "# Cilium debug information\n") 268 } 269 270 func addCiliumVersion(w *tabwriter.Writer, p *models.DebugInfo) { 271 printMD(w, "Cilium version", p.CiliumVersion) 272 } 273 274 func addKernelVersion(w *tabwriter.Writer, p *models.DebugInfo) { 275 printMD(w, "Kernel version", p.KernelVersion) 276 } 277 278 func addCiliumStatus(w *tabwriter.Writer, p *models.DebugInfo) { 279 printMD(w, "Cilium status", "") 280 printTicks(w) 281 pkg.FormatStatusResponse(w, p.CiliumStatus, true, true, true, true) 282 printTicks(w) 283 } 284 285 func addCiliumEnvironmentKeys(w *tabwriter.Writer, p *models.DebugInfo) { 286 printMD(w, "Cilium environment keys", strings.Join(p.EnvironmentVariables, "\n")) 287 } 288 289 func addCiliumEndpointList(w *tabwriter.Writer, p *models.DebugInfo) { 290 printMD(w, "Endpoint list", "") 291 printTicks(w) 292 printEndpointList(w, p.EndpointList) 293 printTicks(w) 294 295 for _, ep := range p.EndpointList { 296 epID := strconv.FormatInt(ep.ID, 10) 297 printList(w, "BPF Policy Get "+epID, "bpf", "policy", "get", epID, "-n") 298 printList(w, "BPF CT List "+epID, "bpf", "ct", "list", epID) 299 printList(w, "Endpoint Get "+epID, "endpoint", "get", epID) 300 printList(w, "Endpoint Health "+epID, "endpoint", "health", epID) 301 printList(w, "Endpoint Log "+epID, "endpoint", "log", epID) 302 303 if ep.Status != nil && ep.Status.Identity != nil { 304 id := strconv.FormatInt(ep.Status.Identity.ID, 10) 305 printList(w, "Identity get "+id, "identity", "get", id) 306 } 307 } 308 } 309 310 func addCiliumServiceList(w *tabwriter.Writer, p *models.DebugInfo) { 311 printMD(w, "Service list", "") 312 printTicks(w) 313 printServiceList(w, p.ServiceList) 314 printTicks(w) 315 } 316 317 func addCiliumPolicy(w *tabwriter.Writer, p *models.DebugInfo) { 318 printMD(w, "Policy get", fmt.Sprintf(":\n %s\nRevision: %d\n", p.Policy.Policy, p.Policy.Revision)) 319 } 320 321 func addSubsystems(w *tabwriter.Writer, p *models.DebugInfo) { 322 for name, status := range p.Subsystem { 323 printMD(w, name, status) 324 } 325 } 326 327 func addCiliumMemoryMap(w *tabwriter.Writer, p *models.DebugInfo) { 328 printMD(w, "Cilium memory map\n", p.CiliumMemoryMap) 329 if nm := p.CiliumNodemonitorMemoryMap; len(nm) > 0 { 330 printMD(w, "Cilium nodemonitor memory map", p.CiliumNodemonitorMemoryMap) 331 } 332 } 333 334 func writeJSONPathToOutput(buf bytes.Buffer, path string, suffix string, jsonPath string) { 335 data := buf.Bytes() 336 db := &models.DebugInfo{} 337 err := db.UnmarshalBinary(data) 338 if err != nil { 339 fmt.Fprintf(os.Stderr, "error unmarshaling binary: %s\n", err) 340 } 341 jsonBytes, err := command.DumpJSONToSlice(db, jsonPath) 342 if err != nil { 343 fmt.Fprintf(os.Stderr, "error printing JSON: %s\n", err) 344 } 345 346 if path == "" { 347 fmt.Println(string(jsonBytes[:])) 348 return 349 } 350 351 fileName := fileName(path, suffix) 352 writeFile(jsonBytes, fileName) 353 354 fmt.Printf("%s output at %s\n", jsonpathOutput, fileName) 355 return 356 357 } 358 359 func writeToOutput(buf bytes.Buffer, output outputType, path string, suffix string) { 360 data := buf.Bytes() 361 362 if path == "" { 363 switch output { 364 case JSONOUTPUT: 365 db := &models.DebugInfo{} 366 err := db.UnmarshalBinary(data) 367 if err != nil { 368 fmt.Fprintf(os.Stderr, "error unmarshaling binary: %s\n", err) 369 } 370 371 err = command.PrintOutputWithType(db, "json") 372 if err != nil { 373 fmt.Fprintf(os.Stderr, "error printing JSON: %s\n", err) 374 } 375 default: 376 fmt.Println(string(data)) 377 } 378 return 379 } 380 381 if output == STDOUT { 382 // Write to standard output 383 fmt.Println(string(data)) 384 return 385 } 386 387 fileName := fileName(path, suffix) 388 389 switch output { 390 case MARKDOWN: 391 // Markdown file 392 writeMarkdown(data, fileName) 393 case HTML: 394 // HTML file 395 writeHTML(data, fileName) 396 case JSONOUTPUT: 397 writeJSON(data, fileName) 398 case JSONPATH: 399 writeJSON(data, fileName) 400 } 401 402 fmt.Printf("%s output at %s\n", outputTypes[output], fileName) 403 } 404 405 func fileName(path, suffix string) string { 406 if len(suffix) == 0 { 407 // no suffix, return path 408 return path 409 } 410 411 ext := filepath.Ext(path) 412 if ext != "" { 413 // insert suffix and move extension to back 414 return fmt.Sprintf("%s-%s%s", strings.TrimSuffix(path, ext), suffix, ext) 415 } 416 // no extension, just append suffix 417 return fmt.Sprintf("%s-%s", path, suffix) 418 } 419 420 func printList(w io.Writer, header string, args ...string) { 421 output, _ := exec.Command("cilium", args...).CombinedOutput() 422 printMD(w, header, string(output)) 423 } 424 425 func printMD(w io.Writer, header string, body string) { 426 if len(body) > 0 { 427 fmt.Fprintf(w, "\n#### %s\n\n```\n%s\n```\n\n", header, body) 428 } else { 429 fmt.Fprintf(w, "\n#### %s\n\n", header) 430 } 431 } 432 433 func printTicks(w io.Writer) { 434 fmt.Fprint(w, "```\n") 435 } 436 437 func writeHTML(data []byte, path string) { 438 output := blackfriday.MarkdownCommon(data) 439 if err := ioutil.WriteFile(path, output, 0644); err != nil { 440 fmt.Fprintf(os.Stderr, "Error while writing HTML file %s", err) 441 return 442 } 443 } 444 445 func writeMarkdown(data []byte, path string) { 446 f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) 447 if err != nil { 448 fmt.Fprintf(os.Stderr, "Could not create file %s", path) 449 } 450 w := tabwriter.NewWriter(f, 5, 0, 3, ' ', 0) 451 w.Write(data) 452 } 453 454 func writeFile(data []byte, path string) { 455 f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) 456 if err != nil { 457 fmt.Fprintf(os.Stderr, "Could not create file %s", path) 458 os.Exit(1) 459 } 460 f.Write(data) 461 } 462 463 func writeJSON(data []byte, path string) { 464 f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) 465 if err != nil { 466 fmt.Fprintf(os.Stderr, "Could not create file %s", path) 467 os.Exit(1) 468 } 469 470 db := &models.DebugInfo{} 471 472 // Unmarshal the binary so we can indent the JSON appropriately when we 473 // display it to end-users. 474 err = db.UnmarshalBinary(data) 475 if err != nil { 476 fmt.Fprintf(os.Stderr, "error unmarshaling binary: %s\n", err) 477 os.Exit(1) 478 } 479 result, err := json.MarshalIndent(db, "", " ") 480 if err != nil { 481 fmt.Fprintf(os.Stderr, "error marshal-indenting data: %s\n", err) 482 os.Exit(1) 483 } 484 f.Write(result) 485 486 }