github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/scripts/generate-sample-config/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "flag" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 "path/filepath" 11 "reflect" 12 "regexp" 13 "strings" 14 "unicode" 15 16 "github.com/sirupsen/logrus" 17 "github.com/spf13/pflag" 18 "github.com/spf13/viper" 19 20 "github.com/pyroscope-io/pyroscope/pkg/cli" 21 "github.com/pyroscope-io/pyroscope/pkg/config" 22 "github.com/pyroscope-io/pyroscope/pkg/util/slices" 23 ) 24 25 // to run this program: 26 // go run scripts/generate-sample-config/main.go -format yaml 27 // go run scripts/generate-sample-config/main.go -format md 28 // or: 29 // go run scripts/generate-sample-config/main.go -directory ../pyroscope.io/docs 30 31 func main() { 32 var ( 33 format string 34 subcommand string 35 directory string 36 ) 37 flag.StringVar(&format, "format", "yaml", "yaml or md") 38 flag.StringVar(&subcommand, "subcommand", "server", "server, agent, exec...") 39 flag.StringVar(&directory, "directory", "", "directory to scan and perform auto replacements") 40 flag.Parse() 41 42 if directory != "" { 43 err := filepath.Walk(directory, func(path string, f os.FileInfo, err error) error { 44 if slices.StringContains([]string{".mdx", ".md"}, filepath.Ext(path)) { 45 return processFile(path) 46 } 47 return nil 48 }) 49 if err != nil { 50 panic(err) 51 } 52 } else { 53 writeConfigDocs(os.Stdout, subcommand, format) 54 } 55 } 56 57 var ( 58 sectionRegexp = regexp.MustCompile(`(?s)<!--\s*generate-sample-config:(.+?):(.+?)\s*-->.*?<!--\s*/generate-sample-config\s*-->`) 59 headerRegexp = regexp.MustCompile("generate-sample-config:(.+?):(.+?)\\s*-") 60 ) 61 62 func processFile(path string) error { 63 content, err := os.ReadFile(path) 64 if err != nil { 65 return fmt.Errorf("reading file: %w", err) 66 } 67 68 newContent := sectionRegexp.ReplaceAllFunc(content, func(b []byte) []byte { 69 submatches := headerRegexp.FindSubmatch(b) 70 buf := bytes.Buffer{} 71 subcommand := string(submatches[1]) 72 format := string(submatches[2]) 73 writeConfigDocs(&buf, subcommand, format) 74 return buf.Bytes() 75 }) 76 77 if bytes.Equal(content, newContent) { 78 return nil 79 } 80 81 fmt.Println(path) 82 return os.WriteFile(path, newContent, 0640) 83 } 84 85 func writeConfigDocs(w io.Writer, subcommand, format string) { 86 flagSet := pflag.NewFlagSet("pyroscope "+subcommand, pflag.ExitOnError) 87 88 v := viper.New() 89 v.SetEnvPrefix("PYROSCOPE") 90 v.AutomaticEnv() 91 v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) 92 93 opts := []cli.FlagOption{ 94 cli.WithReplacement("<supportedProfilers>", "pyspy, rbspy, phpspy, dotnetspy, ebpfspy"), 95 cli.WithSkipDeprecated(true), 96 } 97 98 var val interface{} 99 switch subcommand { 100 case "agent": 101 val = new(config.Agent) 102 // Skip `targets` only from CLI reference. 103 if format == "md" { 104 cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("targets"))...) 105 } else { 106 cli.PopulateFlagSet(val, flagSet, v, opts...) 107 } 108 case "server": 109 val = new(config.Server) 110 // Skip `metrics-export-rules` only from CLI reference. 111 if format == "md" { 112 cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("metrics-export-rules"))...) 113 } else { 114 cli.PopulateFlagSet(val, flagSet, v, opts...) 115 } 116 case "convert": 117 val = new(config.Convert) 118 cli.PopulateFlagSet(val, flagSet, v, opts...) 119 case "exec": 120 val = new(config.Exec) 121 cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("pid"))...) 122 case "connect": 123 val = new(config.Exec) 124 cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("group-name", "user-name", "no-root-drop"))...) 125 case "target": 126 val = new(config.Target) 127 cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("tags"))...) 128 case "metric-export-rule": 129 val = new(config.MetricsExportRule) 130 cli.PopulateFlagSet(val, flagSet, v, opts...) 131 default: 132 //revive:disable-next-line:deep-exit fine for now 133 log.Fatalf("Unknown subcommand %q", subcommand) 134 } 135 136 _, _ = fmt.Fprintf(w, "<!-- generate-sample-config:%s:%s -->\n", subcommand, format) 137 138 switch format { 139 case "yaml": 140 writeYaml(w, flagSet) 141 case "md": 142 writeMarkdown(w, flagSet) 143 default: 144 logrus.Fatalf("Unknown format %q", format) 145 } 146 147 _, _ = fmt.Fprintf(w, "<!-- /generate-sample-config -->") 148 } 149 150 func writeYaml(w io.Writer, flagSet *pflag.FlagSet) { 151 _, _ = fmt.Fprintf(w, "```yaml\n---\n") 152 flagSet.VisitAll(func(f *pflag.Flag) { 153 if f.Name == "config" { 154 return 155 } 156 var v string 157 switch reflect.TypeOf(f.Value).Elem().Kind() { 158 case reflect.Slice, reflect.Map: 159 v = f.Value.String() 160 default: 161 v = fmt.Sprintf("%q", f.Value) 162 } 163 _, _ = fmt.Fprintf(w, "# %s\n%s: %s\n\n", toPrettySentence(f.Usage), f.Name, v) 164 }) 165 _, _ = fmt.Fprintf(w, "```\n") 166 } 167 168 func writeMarkdown(w io.Writer, flagSet *pflag.FlagSet) { 169 _, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", "Name", "Default Value", "Usage") 170 _, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", ":-", ":-", ":-") 171 flagSet.VisitAll(func(f *pflag.Flag) { 172 if f.Name == "config" { 173 return 174 } 175 // Replace vertical bar glyph with HTML code. 176 desc := strings.ReplaceAll(toPrettySentence(f.Usage), "|", `|`) 177 _, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", f.Name, f.DefValue, desc) 178 }) 179 } 180 181 // Capitalizes the first letter and adds period at the end, if necessary. 182 func toPrettySentence(s string) string { 183 if s == "" { 184 return "" 185 } 186 x := []rune(s) 187 x[0] = unicode.ToUpper(x[0]) 188 if x[len(s)-1] != '.' { 189 x = append(x, '.') 190 } 191 return string(x) 192 }