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 }