github.com/yandex/pandora@v0.5.32/components/providers/scenario/config/hcl.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/gohcl"
     9  	"github.com/hashicorp/hcl/v2/hclparse"
    10  	"github.com/spf13/afero"
    11  	"github.com/zclconf/go-cty/cty"
    12  	"github.com/zclconf/go-cty/cty/function"
    13  	"github.com/zclconf/go-cty/cty/function/stdlib"
    14  )
    15  
    16  type AmmoHCL struct {
    17  	VariableSources []SourceHCL   `hcl:"variable_source,block" config:"variable_sources" yaml:"variable_sources"`
    18  	Requests        []RequestHCL  `hcl:"request,block"`
    19  	Calls           []CallHCL     `hcl:"call,block"`
    20  	Scenarios       []ScenarioHCL `hcl:"scenario,block"`
    21  }
    22  
    23  type ScenarioHCL struct {
    24  	Name           string   `hcl:"name,label"`
    25  	Weight         *int64   `hcl:"weight" yaml:"weight,omitempty"`
    26  	MinWaitingTime *int64   `hcl:"min_waiting_time" config:"min_waiting_time" yaml:"min_waiting_time,omitempty"`
    27  	Requests       []string `hcl:"requests" yaml:"requests"`
    28  }
    29  
    30  type SourceHCL struct {
    31  	Name            string             `hcl:"name,label"`
    32  	Type            string             `hcl:"type,label"`
    33  	File            *string            `hcl:"file" yaml:"file,omitempty"`
    34  	Fields          *[]string          `hcl:"fields" yaml:"fields,omitempty"`
    35  	IgnoreFirstLine *bool              `hcl:"ignore_first_line" yaml:"ignore_first_line,omitempty"`
    36  	Delimiter       *string            `hcl:"delimiter" yaml:"delimiter,omitempty"`
    37  	Variables       *map[string]string `hcl:"variables" yaml:"variables,omitempty"`
    38  }
    39  
    40  type RequestHCL struct {
    41  	Name           string                    `hcl:"name,label"`
    42  	Method         string                    `hcl:"method"`
    43  	URI            string                    `hcl:"uri"`
    44  	Headers        map[string]string         `hcl:"headers" yaml:"headers,omitempty"`
    45  	Tag            *string                   `hcl:"tag" yaml:"tag,omitempty"` //TODO: remove
    46  	Body           *string                   `hcl:"body" yaml:"body,omitempty"`
    47  	Preprocessor   *RequestPreprocessorHCL   `hcl:"preprocessor,block" yaml:"preprocessor,omitempty"`
    48  	Postprocessors []RequestPostprocessorHCL `hcl:"postprocessor,block" yaml:"postprocessors,omitempty"`
    49  	Templater      *TemplaterHCL             `hcl:"templater,block" yaml:"templater,omitempty"`
    50  }
    51  
    52  type TemplaterHCL struct {
    53  	Type string `hcl:"type" yaml:"type"`
    54  }
    55  
    56  type AssertSizeHCL struct {
    57  	Val *int    `hcl:"val"`
    58  	Op  *string `hcl:"op"`
    59  }
    60  
    61  type RequestPostprocessorHCL struct {
    62  	Type       string             `hcl:"type,label"`
    63  	Mapping    *map[string]string `hcl:"mapping" yaml:"mapping,omitempty"`
    64  	Headers    *map[string]string `hcl:"headers" yaml:"headers,omitempty"`
    65  	Body       *[]string          `hcl:"body" yaml:"body,omitempty"`
    66  	StatusCode *int               `hcl:"status_code" yaml:"status_code,omitempty"`
    67  	Size       *AssertSizeHCL     `hcl:"size,block" yaml:"size,omitempty"`
    68  }
    69  
    70  type RequestPreprocessorHCL struct {
    71  	//Type    string            `hcl:"type,label"`
    72  	Mapping map[string]string `hcl:"mapping"`
    73  }
    74  
    75  type CallHCL struct {
    76  	Name           string                 `hcl:"name,label"`
    77  	Tag            *string                `hcl:"tag" yaml:"tag,omitempty"`
    78  	Call           string                 `hcl:"call"`
    79  	Metadata       *map[string]string     `hcl:"metadata" yaml:"metadata,omitempty"`
    80  	Payload        string                 `hcl:"payload"`
    81  	Preprocessor   []CallPreprocessorHCL  `hcl:"preprocessor,block" yaml:"preprocessors,omitempty"`
    82  	Postprocessors []CallPostprocessorHCL `hcl:"postprocessor,block" yaml:"postprocessors,omitempty"`
    83  }
    84  
    85  type CallPostprocessorHCL struct {
    86  	Type       string    `hcl:"type,label"`
    87  	Payload    *[]string `hcl:"payload" yaml:"payload,omitempty"`
    88  	StatusCode *int      `hcl:"status_code" yaml:"status_code,omitempty"`
    89  }
    90  
    91  type CallPreprocessorHCL struct {
    92  	Type    string            `hcl:"type,label"`
    93  	Mapping map[string]string `hcl:"mapping"`
    94  }
    95  
    96  func ParseHCLFile(file afero.File) (AmmoHCL, error) {
    97  	const op = "hcl.ParseHCLFile"
    98  
    99  	bytes, err := io.ReadAll(file)
   100  	if err != nil {
   101  		return AmmoHCL{}, fmt.Errorf("%s, io.ReadAll, %w", op, err)
   102  	}
   103  
   104  	parser := hclparse.NewParser()
   105  	f, diag := parser.ParseHCL(bytes, file.Name())
   106  	if diag.HasErrors() {
   107  		return AmmoHCL{}, diag
   108  	}
   109  
   110  	localsBodyContent, remainingBody, diag := f.Body.PartialContent(localsSchema())
   111  	// diag still may have errors, because PartialContent doesn't know about Functions and self-references to locals
   112  	if localsBodyContent == nil || remainingBody == nil {
   113  		return AmmoHCL{}, diag
   114  	}
   115  
   116  	hclContext, diag := decodeLocals(localsBodyContent)
   117  	if diag.HasErrors() {
   118  		return AmmoHCL{}, diag
   119  	}
   120  
   121  	var config AmmoHCL
   122  	diag = gohcl.DecodeBody(remainingBody, hclContext, &config)
   123  	if diag.HasErrors() {
   124  		return AmmoHCL{}, diag
   125  	}
   126  	return config, nil
   127  }
   128  
   129  func decodeLocals(localsBodyContent *hcl.BodyContent) (*hcl.EvalContext, hcl.Diagnostics) {
   130  	vars := map[string]cty.Value{}
   131  	hclContext := buildHclContext(vars)
   132  	for _, block := range localsBodyContent.Blocks {
   133  		if block == nil {
   134  			continue
   135  		}
   136  		if block.Type == "locals" {
   137  			newVars, err := decodeLocalBlock(block, hclContext)
   138  			if err != nil {
   139  				return nil, err
   140  			}
   141  			hclContext = buildHclContext(mergeMaps(vars, newVars))
   142  		}
   143  	}
   144  	return hclContext, nil
   145  }
   146  
   147  func mergeMaps[K comparable, V any](to, from map[K]V) map[K]V {
   148  	for k, v := range from {
   149  		to[k] = v
   150  	}
   151  	return to
   152  }
   153  
   154  func decodeLocalBlock(localsBlock *hcl.Block, hclContext *hcl.EvalContext) (map[string]cty.Value, hcl.Diagnostics) {
   155  	attrs, err := localsBlock.Body.JustAttributes()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	vars := map[string]cty.Value{}
   161  	for name, attr := range attrs {
   162  		val, err := attr.Expr.Value(hclContext)
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  		vars[name] = val
   167  	}
   168  
   169  	return vars, nil
   170  }
   171  
   172  func localsSchema() *hcl.BodySchema {
   173  	return &hcl.BodySchema{
   174  		Blocks: []hcl.BlockHeaderSchema{
   175  			{
   176  				Type:       "locals",
   177  				LabelNames: []string{},
   178  			},
   179  		},
   180  	}
   181  }
   182  
   183  func buildHclContext(vars map[string]cty.Value) *hcl.EvalContext {
   184  	return &hcl.EvalContext{
   185  		Variables: map[string]cty.Value{
   186  			"local": cty.ObjectVal(vars),
   187  		},
   188  		Functions: map[string]function.Function{
   189  			// collection functions
   190  			"coalesce":     stdlib.CoalesceFunc,
   191  			"coalescelist": stdlib.CoalesceListFunc,
   192  			"compact":      stdlib.CompactFunc,
   193  			"concat":       stdlib.ConcatFunc,
   194  			"distinct":     stdlib.DistinctFunc,
   195  			"element":      stdlib.ElementFunc,
   196  			"flatten":      stdlib.FlattenFunc,
   197  			"index":        stdlib.IndexFunc,
   198  			"keys":         stdlib.KeysFunc,
   199  			"lookup":       stdlib.LookupFunc,
   200  			"merge":        stdlib.MergeFunc,
   201  			"reverse":      stdlib.ReverseListFunc,
   202  			"slice":        stdlib.SliceFunc,
   203  			"sort":         stdlib.SortFunc,
   204  			"split":        stdlib.SplitFunc,
   205  			"values":       stdlib.ValuesFunc,
   206  			"zipmap":       stdlib.ZipmapFunc,
   207  		},
   208  	}
   209  }