Gonginx

repository·master·Indexed 42 days ago

https://github.com/tufanbarisyildirim/gonginx

A Go library for parsing, manipulating, and regenerating Nginx configuration files. It provides a lexer and parser to convert Nginx configs into Go objects (AST), allowing developers to programmatically manage settings such as load balancer upstreams, server blocks, and listen ports.

Tokens
4.6K
Snippets
17
Records
29
Agent score
52%

What's inside Gonginx

  1. Overview of Gonginx

    master
    Gonginx is an Nginx configuration parser designed for Go applications. It allows developers to parse, edit, and regenerate Nginx configuration files programmatically. This is particularly useful for managing dynamic balancer configurations within a Go-based infrastructure.
  2. Understand Parent-Child Directive Relationships

    master

    Gonginx maintains a hierarchical structure in its AST (Abstract Syntax Tree) to allow for easier manipulation of nested configurations:

    • Root-level directives: Leaf directives located at the root of the configuration have a nil parent.
    • Nested directives: Every nested directive points to its enclosing directive wrapper (for example, a location directive will point to its enclosing server directive, which in turn points to http).
  3. Configure Include Parsing behavior

    master

    By default, Gonginx handles includes. You can customize this behavior using parser options:

    • Enable include parsing: Use parser.WithIncludeParsing().
    • Include deduplication: Includes are automatically deduplicated by their canonical file path.
    • Cycle detection: Cyclic include branches are skipped by default to prevent infinite recursion. To return an explicit error when a cycle is detected, use parser.WithIncludeCycleErr().
  4. Core Components of Gonginx

    master

    Gonginx is composed of three primary functional areas:

    • Parser: The main engine that analyzes Nginx structured files and converts them into Go objects. It utilizes a lexer to generate tokens and a parser to transform those tokens into configuration objects.
    • Config: Provides the Go representations (models and AST) for any context, directive, or parameter within an Nginx configuration.
    • Dumper: Handles the styling and formatting configuration for regenerating (dumping) the configuration files.
  5. How Upstream Lookup modes work

    master

    When searching for upstream blocks, Gonginx offers two modes:

    • FindUpstreams(): Permissive mode. It skips unexpected types and continues searching.
    • FindUpstreamsStrict(): Strict mode. It returns a typed error if it encounters unexpected upstream directive types.
  6. Parse an Nginx configuration string

    master

    To parse Nginx configuration content provided as a string, use parser.NewStringParser(fullConf) followed by the .Parse() method. This is useful for manipulating configuration snippets in memory.

    p := parser.NewStringParser(fullConf)
    conf, err := p.Parse()
    if err != nil {
    	panic(err)
    }
  7. Dump Nginx configuration to a string or file

    master

    Gonginx provides two ways to regenerate Nginx configuration from a config.Config object:

    1. To a string: Use dumper.DumpConfig(conf, style). Use dumper.IndentedStyle for human-readable output.
    2. To a file: Use dumper.WriteConfig(conf, style, overwrite). You can set the target path on the config object itself using conf.FilePath = filePath before calling this.

    Available styles include dumper.IndentedStyle.

    // Dump to string
    dumpString := dumper.DumpConfig(conf, dumper.IndentedStyle)
    
    // Dump to file
    conf.FilePath = "/path/to/nginx.conf"
    dumper.WriteConfig(conf, dumper.IndentedStyle, false)
  8. Parse an Nginx configuration file

    master

    To parse an existing Nginx configuration file from disk, use parser.NewParser(filePath) followed by the .Parse() method. This returns a config.Config object representing the parsed structure.

    func parseConfigAndGetPorts(filePath string) ([]string, error) {
    	p, err := parser.NewParser(filePath)
    	if err != nil {
    		return nil, fmt.Errorf("failed to create parser: %w", err)
    	}
    	conf, err := p.Parse()
    	if err != nil {
    		return nil, fmt.Errorf("failed to parse config: %w", err)
    	}
    	// ... use conf to find directives
    }
  9. Configure Dumper Styling and Sorting

    master

    The dumper package manages how the configuration is written back to a file.

    • Non-mutating Sort: Using dumper.NoIndentSortedStyle or other sorted dump options will sort the output, but it will not mutate the order of directives in the in-memory AST. This allows you to maintain a specific logical order in your code while producing a formatted output.
  10. Configure Dumper output style

    master

    Use the Style struct to control the formatting of the dumped configuration. You can use predefined styles or create a custom one.

    Predefined Styles:

    • NoIndentStyle: No indentation, no sorting.
    • IndentStyle: Indentation of 4 spaces.
    • NoIndentSortedStyle: No indentation, but directives are sorted.
    • NoIndentSortedSpaceStyle: No indentation, directives are sorted, and spaces are added before blocks.

    Customization Fields:

    • SortDirectives bool: Whether to sort directives.
    • SpaceBeforeBlocks bool: Whether to add space before blocks.
    • StartIndent int: Initial indentation level.
    • Indent int: Indentation level for nested elements.
    • Debug bool: Enable debug mode.
    • DisableLuaFormatting bool: Disable Lua-specific formatting.
    • LuaFormatter LuaFormatterFunc: Custom function for formatting Lua blocks.

    Style Methods:

    • WithLuaFormatting(enabled bool) *Style
    • WithLuaFormatter(formatter LuaFormatterFunc) *Style
    // Example using a predefined style
    output := dumper.DumpConfig(myConfig, dumper.IndentStyle)
  11. Add a custom directive to any block

    master

    To add a completely new directive to an existing block, find the target block, cast it to a *config.Block, and append a new *config.Directive to its Directives slice.

    blocks := conf.FindDirectives(blockName)
    block := blocks[0].GetBlock()
    realBlock := block.(*config.Block)
    
    newDirective := &config.Directive{
    	Name:       directiveName,
    	Parameters: []string{directiveValue},
    }
    realBlock.Directives = append(realBlock.Directives, newDirective)
  12. Update an existing directive in place

    master

    To modify an existing directive (e.g., changing a proxy_pass URL), find the directive using FindDirectives(name), iterate through the results, and update the Parameters slice directly.

    directives := c.FindDirectives("proxy_pass")
    for _, directive := range directives {
    	if directive.GetParameters()[0] == "http://old-url.com/" {
    		directive.GetParameters()[0] = "http://new-url.com/"
    	}
    }