github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/tools/doc-generator/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "path/filepath" 8 "reflect" 9 "strings" 10 "text/template" 11 12 "github.com/grafana/dskit/kv/consul" 13 "github.com/grafana/dskit/kv/etcd" 14 "github.com/grafana/dskit/kv/memberlist" 15 "github.com/weaveworks/common/server" 16 17 "github.com/cortexproject/cortex/pkg/alertmanager" 18 "github.com/cortexproject/cortex/pkg/alertmanager/alertstore" 19 "github.com/cortexproject/cortex/pkg/chunk" 20 "github.com/cortexproject/cortex/pkg/chunk/cache" 21 "github.com/cortexproject/cortex/pkg/chunk/purger" 22 "github.com/cortexproject/cortex/pkg/chunk/storage" 23 "github.com/cortexproject/cortex/pkg/compactor" 24 "github.com/cortexproject/cortex/pkg/configs" 25 config_client "github.com/cortexproject/cortex/pkg/configs/client" 26 "github.com/cortexproject/cortex/pkg/cortex" 27 "github.com/cortexproject/cortex/pkg/distributor" 28 "github.com/cortexproject/cortex/pkg/flusher" 29 "github.com/cortexproject/cortex/pkg/frontend" 30 "github.com/cortexproject/cortex/pkg/ingester" 31 "github.com/cortexproject/cortex/pkg/ingester/client" 32 "github.com/cortexproject/cortex/pkg/querier" 33 "github.com/cortexproject/cortex/pkg/querier/queryrange" 34 querier_worker "github.com/cortexproject/cortex/pkg/querier/worker" 35 "github.com/cortexproject/cortex/pkg/ruler" 36 "github.com/cortexproject/cortex/pkg/ruler/rulestore" 37 "github.com/cortexproject/cortex/pkg/storage/bucket/s3" 38 "github.com/cortexproject/cortex/pkg/storage/tsdb" 39 "github.com/cortexproject/cortex/pkg/storegateway" 40 "github.com/cortexproject/cortex/pkg/util/validation" 41 ) 42 43 const ( 44 maxLineWidth = 80 45 tabWidth = 2 46 ) 47 48 var ( 49 // Ordered list of root blocks. The order is the same order that will 50 // follow the markdown generation. 51 rootBlocks = []rootBlock{ 52 { 53 name: "server_config", 54 structType: reflect.TypeOf(server.Config{}), 55 desc: "The server_config configures the HTTP and gRPC server of the launched service(s).", 56 }, 57 { 58 name: "distributor_config", 59 structType: reflect.TypeOf(distributor.Config{}), 60 desc: "The distributor_config configures the Cortex distributor.", 61 }, 62 { 63 name: "ingester_config", 64 structType: reflect.TypeOf(ingester.Config{}), 65 desc: "The ingester_config configures the Cortex ingester.", 66 }, 67 { 68 name: "querier_config", 69 structType: reflect.TypeOf(querier.Config{}), 70 desc: "The querier_config configures the Cortex querier.", 71 }, 72 { 73 name: "query_frontend_config", 74 structType: reflect.TypeOf(frontend.CombinedFrontendConfig{}), 75 desc: "The query_frontend_config configures the Cortex query-frontend.", 76 }, 77 { 78 name: "query_range_config", 79 structType: reflect.TypeOf(queryrange.Config{}), 80 desc: "The query_range_config configures the query splitting and caching in the Cortex query-frontend.", 81 }, 82 { 83 name: "ruler_config", 84 structType: reflect.TypeOf(ruler.Config{}), 85 desc: "The ruler_config configures the Cortex ruler.", 86 }, 87 { 88 name: "ruler_storage_config", 89 structType: reflect.TypeOf(rulestore.Config{}), 90 desc: "The ruler_storage_config configures the Cortex ruler storage backend.", 91 }, 92 { 93 name: "alertmanager_config", 94 structType: reflect.TypeOf(alertmanager.MultitenantAlertmanagerConfig{}), 95 desc: "The alertmanager_config configures the Cortex alertmanager.", 96 }, 97 { 98 name: "alertmanager_storage_config", 99 structType: reflect.TypeOf(alertstore.Config{}), 100 desc: "The alertmanager_storage_config configures the Cortex alertmanager storage backend.", 101 }, 102 { 103 name: "table_manager_config", 104 structType: reflect.TypeOf(chunk.TableManagerConfig{}), 105 desc: "The table_manager_config configures the Cortex table-manager.", 106 }, 107 { 108 name: "storage_config", 109 structType: reflect.TypeOf(storage.Config{}), 110 desc: "The storage_config configures where Cortex stores the data (chunks storage engine).", 111 }, 112 { 113 name: "flusher_config", 114 structType: reflect.TypeOf(flusher.Config{}), 115 desc: "The flusher_config configures the WAL flusher target, used to manually run one-time flushes when scaling down ingesters.", 116 }, 117 { 118 name: "chunk_store_config", 119 structType: reflect.TypeOf(chunk.StoreConfig{}), 120 desc: "The chunk_store_config configures how Cortex stores the data (chunks storage engine).", 121 }, 122 { 123 name: "ingester_client_config", 124 structType: reflect.TypeOf(client.Config{}), 125 desc: "The ingester_client_config configures how the Cortex distributors connect to the ingesters.", 126 }, 127 { 128 name: "frontend_worker_config", 129 structType: reflect.TypeOf(querier_worker.Config{}), 130 desc: "The frontend_worker_config configures the worker - running within the Cortex querier - picking up and executing queries enqueued by the query-frontend or query-scheduler.", 131 }, 132 { 133 name: "etcd_config", 134 structType: reflect.TypeOf(etcd.Config{}), 135 desc: "The etcd_config configures the etcd client.", 136 }, 137 { 138 name: "consul_config", 139 structType: reflect.TypeOf(consul.Config{}), 140 desc: "The consul_config configures the consul client.", 141 }, 142 { 143 name: "memberlist_config", 144 structType: reflect.TypeOf(memberlist.KVConfig{}), 145 desc: "The memberlist_config configures the Gossip memberlist.", 146 }, 147 { 148 name: "limits_config", 149 structType: reflect.TypeOf(validation.Limits{}), 150 desc: "The limits_config configures default and per-tenant limits imposed by Cortex services (ie. distributor, ingester, ...).", 151 }, 152 { 153 name: "redis_config", 154 structType: reflect.TypeOf(cache.RedisConfig{}), 155 desc: "The redis_config configures the Redis backend cache.", 156 }, 157 { 158 name: "memcached_config", 159 structType: reflect.TypeOf(cache.MemcachedConfig{}), 160 desc: "The memcached_config block configures how data is stored in Memcached (ie. expiration).", 161 }, 162 { 163 name: "memcached_client_config", 164 structType: reflect.TypeOf(cache.MemcachedClientConfig{}), 165 desc: "The memcached_client_config configures the client used to connect to Memcached.", 166 }, 167 { 168 name: "fifo_cache_config", 169 structType: reflect.TypeOf(cache.FifoCacheConfig{}), 170 desc: "The fifo_cache_config configures the local in-memory cache.", 171 }, 172 { 173 name: "configs_config", 174 structType: reflect.TypeOf(configs.Config{}), 175 desc: "The configs_config configures the Cortex Configs DB and API.", 176 }, 177 { 178 name: "configstore_config", 179 structType: reflect.TypeOf(config_client.Config{}), 180 desc: "The configstore_config configures the config database storing rules and alerts, and is used by the Cortex alertmanager.", 181 }, 182 { 183 name: "blocks_storage_config", 184 structType: reflect.TypeOf(tsdb.BlocksStorageConfig{}), 185 desc: "The blocks_storage_config configures the blocks storage.", 186 }, 187 { 188 name: "compactor_config", 189 structType: reflect.TypeOf(compactor.Config{}), 190 desc: "The compactor_config configures the compactor for the blocks storage.", 191 }, 192 { 193 name: "store_gateway_config", 194 structType: reflect.TypeOf(storegateway.Config{}), 195 desc: "The store_gateway_config configures the store-gateway service used by the blocks storage.", 196 }, 197 { 198 name: "purger_config", 199 structType: reflect.TypeOf(purger.Config{}), 200 desc: "The purger_config configures the purger which takes care of delete requests.", 201 }, 202 { 203 name: "s3_sse_config", 204 structType: reflect.TypeOf(s3.SSEConfig{}), 205 desc: "The s3_sse_config configures the S3 server-side encryption.", 206 }, 207 } 208 ) 209 210 func removeFlagPrefix(block *configBlock, prefix string) { 211 for _, entry := range block.entries { 212 switch entry.kind { 213 case "block": 214 // Skip root blocks 215 if !entry.root { 216 removeFlagPrefix(entry.block, prefix) 217 } 218 case "field": 219 if strings.HasPrefix(entry.fieldFlag, prefix) { 220 entry.fieldFlag = "<prefix>" + entry.fieldFlag[len(prefix):] 221 } 222 } 223 } 224 } 225 226 func annotateFlagPrefix(blocks []*configBlock) { 227 // Find duplicated blocks 228 groups := map[string][]*configBlock{} 229 for _, block := range blocks { 230 groups[block.name] = append(groups[block.name], block) 231 } 232 233 // For each duplicated block, we need to fix the CLI flags, because 234 // in the documentation each block will be displayed only once but 235 // since they're duplicated they will have a different CLI flag 236 // prefix, which we want to correctly document. 237 for _, group := range groups { 238 if len(group) == 1 { 239 continue 240 } 241 242 // We need to find the CLI flags prefix of each config block. To do it, 243 // we pick the first entry from each config block and then find the 244 // different prefix across all of them. 245 flags := []string{} 246 for _, block := range group { 247 for _, entry := range block.entries { 248 if entry.kind == "field" { 249 flags = append(flags, entry.fieldFlag) 250 break 251 } 252 } 253 } 254 255 allPrefixes := []string{} 256 for i, prefix := range findFlagsPrefix(flags) { 257 group[i].flagsPrefix = prefix 258 allPrefixes = append(allPrefixes, prefix) 259 } 260 261 // Store all found prefixes into each block so that when we generate the 262 // markdown we also know which are all the prefixes for each root block. 263 for _, block := range group { 264 block.flagsPrefixes = allPrefixes 265 } 266 } 267 268 // Finally, we can remove the CLI flags prefix from the blocks 269 // which have one annotated. 270 for _, block := range blocks { 271 if block.flagsPrefix != "" { 272 removeFlagPrefix(block, block.flagsPrefix) 273 } 274 } 275 } 276 277 func generateBlocksMarkdown(blocks []*configBlock) string { 278 md := &markdownWriter{} 279 md.writeConfigDoc(blocks) 280 return md.string() 281 } 282 283 func generateBlockMarkdown(blocks []*configBlock, blockName, fieldName string) string { 284 // Look for the requested block. 285 for _, block := range blocks { 286 if block.name != blockName { 287 continue 288 } 289 290 md := &markdownWriter{} 291 292 // Wrap the root block with another block, so that we can show the name of the 293 // root field containing the block specs. 294 md.writeConfigBlock(&configBlock{ 295 name: blockName, 296 desc: block.desc, 297 entries: []*configEntry{ 298 { 299 kind: "block", 300 name: fieldName, 301 required: true, 302 block: block, 303 blockDesc: "", 304 root: false, 305 }, 306 }, 307 }) 308 309 return md.string() 310 } 311 312 // If the block has not been found, we return an empty string. 313 return "" 314 } 315 316 func main() { 317 // Parse the generator flags. 318 flag.Parse() 319 if flag.NArg() != 1 { 320 fmt.Fprintf(os.Stderr, "Usage: doc-generator template-file") 321 os.Exit(1) 322 } 323 324 templatePath := flag.Arg(0) 325 326 // In order to match YAML config fields with CLI flags, we do map 327 // the memory address of the CLI flag variables and match them with 328 // the config struct fields address. 329 cfg := &cortex.Config{} 330 flags := parseFlags(cfg) 331 332 // Parse the config, mapping each config field with the related CLI flag. 333 blocks, err := parseConfig(nil, cfg, flags) 334 if err != nil { 335 fmt.Fprintf(os.Stderr, "An error occurred while generating the doc: %s\n", err.Error()) 336 os.Exit(1) 337 } 338 339 // Annotate the flags prefix for each root block, and remove the 340 // prefix wherever encountered in the config blocks. 341 annotateFlagPrefix(blocks) 342 343 // Generate documentation markdown. 344 data := struct { 345 ConfigFile string 346 BlocksStorageConfigBlock string 347 StoreGatewayConfigBlock string 348 CompactorConfigBlock string 349 QuerierConfigBlock string 350 S3SSEConfigBlock string 351 GeneratedFileWarning string 352 }{ 353 ConfigFile: generateBlocksMarkdown(blocks), 354 BlocksStorageConfigBlock: generateBlockMarkdown(blocks, "blocks_storage_config", "blocks_storage"), 355 StoreGatewayConfigBlock: generateBlockMarkdown(blocks, "store_gateway_config", "store_gateway"), 356 CompactorConfigBlock: generateBlockMarkdown(blocks, "compactor_config", "compactor"), 357 QuerierConfigBlock: generateBlockMarkdown(blocks, "querier_config", "querier"), 358 S3SSEConfigBlock: generateBlockMarkdown(blocks, "s3_sse_config", "sse"), 359 GeneratedFileWarning: "<!-- DO NOT EDIT THIS FILE - This file has been automatically generated from its .template -->", 360 } 361 362 // Load the template file. 363 tpl := template.New(filepath.Base(templatePath)) 364 tpl, err = tpl.ParseFiles(templatePath) 365 if err != nil { 366 fmt.Fprintf(os.Stderr, "An error occurred while loading the template %s: %s\n", templatePath, err.Error()) 367 os.Exit(1) 368 } 369 370 // Execute the template to inject generated doc. 371 if err := tpl.Execute(os.Stdout, data); err != nil { 372 fmt.Fprintf(os.Stderr, "An error occurred while executing the template %s: %s\n", templatePath, err.Error()) 373 os.Exit(1) 374 } 375 }