Skip to content

Commit

Permalink
scenario hcl
Browse files Browse the repository at this point in the history
scenario hcl
  • Loading branch information
oke11o committed Sep 12, 2023
1 parent 61789aa commit cd2797e
Show file tree
Hide file tree
Showing 9 changed files with 895 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
"components/providers/http/util/request_test.go":"load/projects/pandora/components/providers/http/util/request_test.go",
"components/providers/http_scenario/ammo.go":"load/projects/pandora/components/providers/http_scenario/ammo.go",
"components/providers/http_scenario/ammo_config.go":"load/projects/pandora/components/providers/http_scenario/ammo_config.go",
"components/providers/http_scenario/ammo_hcl.go":"load/projects/pandora/components/providers/http_scenario/ammo_hcl.go",
"components/providers/http_scenario/ammo_hcl_test.go":"load/projects/pandora/components/providers/http_scenario/ammo_hcl_test.go",
"components/providers/http_scenario/decode.go":"load/projects/pandora/components/providers/http_scenario/decode.go",
"components/providers/http_scenario/decode_sample_config_test.golden.hcl":"load/projects/pandora/components/providers/http_scenario/decode_sample_config_test.golden.hcl",
"components/providers/http_scenario/decode_sample_config_test.hcl":"load/projects/pandora/components/providers/http_scenario/decode_sample_config_test.hcl",
Expand Down Expand Up @@ -286,6 +288,8 @@
"lib/netutil/mocks/dns_cache.go":"load/projects/pandora/lib/netutil/mocks/dns_cache.go",
"lib/netutil/netutil_suite_test.go":"load/projects/pandora/lib/netutil/netutil_suite_test.go",
"lib/netutil/validator.go":"load/projects/pandora/lib/netutil/validator.go",
"lib/str/format.go":"load/projects/pandora/lib/str/format.go",
"lib/str/format_test.go":"load/projects/pandora/lib/str/format_test.go",
"lib/str/string.go":"load/projects/pandora/lib/str/string.go",
"lib/str/string_test.go":"load/projects/pandora/lib/str/string_test.go",
"lib/tag/debug.go":"load/projects/pandora/lib/tag/debug.go",
Expand Down
2 changes: 1 addition & 1 deletion cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"go.uber.org/zap/zapcore"
)

const Version = "0.5.8"
const Version = "0.5.9"
const defaultConfigFile = "load"
const stdinConfigSelector = "-"

Expand Down
360 changes: 360 additions & 0 deletions components/providers/http_scenario/ammo_hcl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
package httpscenario

import (
"fmt"
"io"

"github.com/hashicorp/hcl/v2/hclsimple"
"github.com/spf13/afero"
"github.com/yandex/pandora/components/providers/http_scenario/postprocessor"
"github.com/yandex/pandora/lib/str"
)

type AmmoHCL struct {
VariableSources []SourceHCL `hcl:"variable_source,block"`
Requests []RequestHCL `hcl:"request,block"`
Scenarios []ScenarioHCL `hcl:"scenario,block"`
}

type SourceHCL struct {
Name string `hcl:"name,label"`
Type string `hcl:"type,label"`
File *string `hcl:"file"`
Fields *[]string `hcl:"fields"`
IgnoreFirstLine *bool `hcl:"ignore_first_line"`
Delimiter *string `hcl:"delimiter"`
Variables *map[string]string `hcl:"variables"`
}

type RequestHCL struct {
Name string `hcl:"name,label"`
Method string `hcl:"method"`
Headers map[string]string `hcl:"headers"`
Tag *string `hcl:"tag"`
Body *string `hcl:"body"`
URI string `hcl:"uri"`
Preprocessor *PreprocessorHCL `hcl:"preprocessor,block"`
Postprocessors []PostprocessorHCL `hcl:"postprocessor,block"`
Templater *string `hcl:"templater"`
}

type ScenarioHCL struct {
Name string `hcl:"name,label"`
Weight int64 `hcl:"weight"`
MinWaitingTime int64 `hcl:"min_waiting_time"`
Shoots []string `hcl:"shoot"`
}

type AssertSizeHCL struct {
Val *int `hcl:"val"`
Op *string `hcl:"op"`
}

type PostprocessorHCL struct {
Type string `hcl:"type,label"`
Mapping *map[string]string `hcl:"mapping"`
Headers *map[string]string `hcl:"headers"`
Body *[]string `hcl:"body"`
StatusCode *int `hcl:"status_code"`
Size *AssertSizeHCL `hcl:"size,block"`
}

type PreprocessorHCL struct {
Mapping map[string]string `hcl:"mapping"`
}

func ParseHCLFile(file afero.File) (AmmoHCL, error) {
const op = "hcl.ParseHCLFile"

var config AmmoHCL
bytes, err := io.ReadAll(file)
if err != nil {
return AmmoHCL{}, fmt.Errorf("%s, io.ReadAll, %w", op, err)
}
err = hclsimple.Decode(file.Name(), bytes, nil, &config)
if err != nil {
return AmmoHCL{}, fmt.Errorf("%s, hclsimple.Decode, %w", op, err)
}
return config, nil
}

func ConvertHCLToAmmo(ammo AmmoHCL, fs afero.Fs) (AmmoConfig, error) {
const op = "scenario.ConvertHCLToAmmo"

var sources []VariableSource
if len(ammo.VariableSources) > 0 {
sources = make([]VariableSource, len(ammo.VariableSources))
for i, s := range ammo.VariableSources {
file := ""
if s.File != nil {
file = *s.File
}
switch s.Type {
case "file/json":
sources[i] = &VariableSourceJSON{
Name: s.Name,
File: file,
fs: fs,
}
case "file/csv":
var fields []string
if s.Fields != nil {
fields = make([]string, len(*s.Fields))
copy(fields, *s.Fields)
}
skipHeader := false
if s.IgnoreFirstLine != nil {
skipHeader = *s.IgnoreFirstLine
}
headerAsFields := ""
if s.Delimiter != nil {
headerAsFields = *s.Delimiter
}
sources[i] = &VariableSourceCsv{
Name: s.Name,
File: file,
Fields: fields,
IgnoreFirstLine: skipHeader,
Delimiter: headerAsFields,
fs: fs,
}
default:
return AmmoConfig{}, fmt.Errorf("%s, unknown variable source type: %s", op, s.Type)
}
}
}

var requests []RequestConfig
if len(ammo.Requests) > 0 {
requests = make([]RequestConfig, len(ammo.Requests))
for i, r := range ammo.Requests {
var postprocessors []postprocessor.Postprocessor
if len(r.Postprocessors) > 0 {
postprocessors = make([]postprocessor.Postprocessor, len(r.Postprocessors))
for j, p := range r.Postprocessors {
switch p.Type {
case "var/header":
postprocessors[j] = &postprocessor.VarHeaderPostprocessor{
Mapping: *p.Mapping,
}
case "var/xpath":
postprocessors[j] = &postprocessor.VarXpathPostprocessor{
Mapping: *p.Mapping,
}
case "var/jsonpath":
postprocessors[j] = &postprocessor.VarJsonpathPostprocessor{
Mapping: *p.Mapping,
}
case "assert/response":
postp := &postprocessor.AssertResponse{}
if p.Headers != nil {
postp.Headers = *p.Headers
}
if p.Body != nil {
postp.Body = *p.Body
}
if p.StatusCode != nil {
postp.StatusCode = *p.StatusCode
}
if p.Size != nil {
postp.Size = &postprocessor.AssertSize{}
if p.Size.Val != nil {
postp.Size.Val = *p.Size.Val
}
if p.Size.Op != nil {
postp.Size.Op = *p.Size.Op
}
}
if err := postp.Validate(); err != nil {
return AmmoConfig{}, fmt.Errorf("%s, invalid postprocessor.AssertResponse %w", op, err)
}
postprocessors[j] = postp
default:
return AmmoConfig{}, fmt.Errorf("%s, unknown postprocessor type: %s", op, p.Type)
}
}
}
templater := NewTextTemplater()
if r.Templater != nil && *r.Templater == "html" {
templater = NewHTMLTemplater()
}
tag := ""
if r.Tag != nil {
tag = *r.Tag
}
var variables map[string]string
if r.Preprocessor != nil {
variables = r.Preprocessor.Mapping
}
requests[i] = RequestConfig{
Name: r.Name,
Method: r.Method,
Headers: r.Headers,
Tag: tag,
Body: r.Body,
URI: r.URI,
Preprocessor: Preprocessor{Mapping: variables},
Postprocessors: postprocessors,
Templater: templater,
}
}
}

var scenarios []ScenarioConfig
if len(ammo.Scenarios) > 0 {
scenarios = make([]ScenarioConfig, len(ammo.Scenarios))
for i, s := range ammo.Scenarios {
scenarios[i] = ScenarioConfig(s)
}
}

result := AmmoConfig{
VariableSources: sources,
Requests: requests,
Scenarios: scenarios,
}

return result, nil
}

func ConvertAmmoToHCL(ammo AmmoConfig) (AmmoHCL, error) {
const op = "scenario.ConvertHCLToAmmo"

var sources []SourceHCL
if len(ammo.VariableSources) > 0 {
sources = make([]SourceHCL, len(ammo.VariableSources))
for i, s := range ammo.VariableSources {
switch val := s.(type) {
case *VariableSourceVariables:
var variables map[string]string
if val.Variables != nil {
variables = make(map[string]string, len(val.Variables))
for k, va := range val.Variables {
variables[k] = str.FormatString(va)
}
}
v := SourceHCL{
Type: "variables",
Name: val.Name,
Variables: &variables,
}
sources[i] = v
case *VariableSourceJSON:
file := val.File
v := SourceHCL{
Type: "file/json",
Name: val.Name,
File: &file,
}
sources[i] = v
case *VariableSourceCsv:
var fields *[]string
if val.Fields != nil {
f := val.Fields
fields = &f
}
ignoreFirstLine := val.IgnoreFirstLine
delimiter := val.Delimiter
file := val.File
v := SourceHCL{
Type: "file/csv",
Name: val.Name,
File: &file,
Fields: fields,
IgnoreFirstLine: &ignoreFirstLine,
Delimiter: &delimiter,
}
sources[i] = v
default:
return AmmoHCL{}, fmt.Errorf("%s variable source type %T not supported", op, val)
}
}

}
var requests []RequestHCL
if len(ammo.Requests) > 0 {
requests = make([]RequestHCL, len(ammo.Requests))
for i, r := range ammo.Requests {
var postprocessors []PostprocessorHCL
if len(r.Postprocessors) > 0 {
postprocessors = make([]PostprocessorHCL, len(r.Postprocessors))
for j, p := range r.Postprocessors {
switch val := p.(type) {
case *postprocessor.VarHeaderPostprocessor:
postprocessors[j] = PostprocessorHCL{
Type: "var/header",
Mapping: &val.Mapping,
}
case *postprocessor.VarXpathPostprocessor:
postprocessors[j] = PostprocessorHCL{
Type: "var/xpath",
Mapping: &val.Mapping,
}
case *postprocessor.VarJsonpathPostprocessor:
postprocessors[j] = PostprocessorHCL{
Type: "var/jsonpath",
Mapping: &val.Mapping,
}
case *postprocessor.AssertResponse:
postprocessors[j] = PostprocessorHCL{
Type: "assert/response",
Headers: &val.Headers,
Body: &val.Body,
StatusCode: &val.StatusCode,
}
if val.Size != nil {
postprocessors[j].Size = &AssertSizeHCL{
Val: &val.Size.Val,
Op: &val.Size.Op,
}
}
if e := val.Validate(); e != nil {
return AmmoHCL{}, fmt.Errorf("%s postprocessor assert/response validation failed: %w", op, e)
}
default:
return AmmoHCL{}, fmt.Errorf("%s postprocessor type %T not supported", op, val)
}
}
}

req := RequestHCL{
Name: r.Name,
URI: r.URI,
Method: r.Method,
Headers: r.Headers,
Body: r.Body,
Postprocessors: postprocessors,
}
if r.Preprocessor.Mapping != nil {
req.Preprocessor = &PreprocessorHCL{Mapping: r.Preprocessor.Mapping}
}
tag := r.Tag
if tag != "" {
req.Tag = &tag
}
templater := "text"
_, ok := r.Templater.(*HTMLTemplater)
if ok {
templater = "html"
}
req.Templater = &templater

requests[i] = req
}
}
var scenarios []ScenarioHCL
if len(ammo.Scenarios) > 0 {
scenarios = make([]ScenarioHCL, len(ammo.Scenarios))
for i, s := range ammo.Scenarios {
scenarios[i] = ScenarioHCL(s)
}
}

result := AmmoHCL{
VariableSources: sources,
Requests: requests,
Scenarios: scenarios,
}

return result, nil
}
Loading

0 comments on commit cd2797e

Please sign in to comment.