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  }