diff --git a/test/convert.go b/test/convert.go index a27e9136..c26322d3 100644 --- a/test/convert.go +++ b/test/convert.go @@ -25,6 +25,10 @@ import ( "github.com/haproxytech/client-native/v6/models" ) +//go:embed expected/structured.json +var expectedStructuredV2JSON []byte +var expectedStructuredV2 map[string]interface{} + //go:embed expected/structured.json var expectedStructuredJSON []byte var expectedStructured map[string]interface{} @@ -33,390 +37,541 @@ var onceStrutructured sync.Once func initStructuredExpected() { onceStrutructured.Do(func() { - err := json.Unmarshal(expectedStructuredJSON, &expectedStructured) + err := json.Unmarshal(expectedStructuredV2JSON, &expectedStructuredV2) + if err != nil { + panic(err) + } + err = json.Unmarshal(expectedStructuredJSON, &expectedStructured) if err != nil { panic(err) } }) } -func expectedResources[T any](res T, elementKey string) error { +func expectedResources[T any](elementKey string) (map[string]T, error) { v := expectedStructured[elementKey] j, err := json.Marshal(v) if err != nil { - return err + return nil, err } - err = json.Unmarshal(j, &res) + var elems map[string]T + err = json.Unmarshal(j, &elems) if err != nil { - return err + // Case Defaults, Globals + var elem T + err = json.Unmarshal(j, &elem) + if err != nil { + return nil, err + } + return map[string]T{"": elem}, err } - return nil + + return elems, nil } -func expectedChildResources[P, T any](res map[string]T, parentKey, parentNameKey, elementKey string) error { +func expectedChildResources[P, T any](res map[string][]T, parentKey, parentNameKey, elementKey string) error { v := expectedStructured[parentKey] - vlist, ok := v.([]interface{}) - if ok { - for _, p := range vlist { - pj, err := json.Marshal(p) - if err != nil { - return err - } - var parent P - err = json.Unmarshal(pj, &parent) - if err != nil { - return err - } + parentMap, ok := v.(map[string]interface{}) + if !ok { + return fmt.Errorf("expectedStructuredV3[%s] is not a map[string]interface{}", parentKey) + } - pmap := p.(map[string]interface{}) - pName, ok := pmap[parentNameKey] - if !ok { - continue - } - var pkey string - switch parentKey { - case "frontends": - pkey = configuration.FrontendParentName - case "backends": - pkey = configuration.BackendParentName - case "fcgi_apps": - pkey = configuration.FCGIAppParentName - case "log_forwards": - pkey = configuration.LogForwardParentName - case "userlists": - pkey = "userlist" - case "mailers_sections": - pkey = "mailers_sections" - case "resolvers": - pkey = configuration.ResolverParentName - case "peers": - pkey = configuration.PeersParentName - case "rings": - pkey = configuration.RingParentName - } - key := fmt.Sprintf("%s/%s", pkey, pName) + for pname, pmvalue := range parentMap { + var pkey string + switch parentKey { + case "frontends": + pkey = configuration.FrontendParentName + case "backends": + pkey = configuration.BackendParentName + case "fcgi_apps": + pkey = configuration.FCGIAppParentName + case "log_forwards": + pkey = configuration.LogForwardParentName + case "userlists": + pkey = "userlist" + case "mailers_sections": + pkey = "mailers_sections" + case "resolvers": + pkey = configuration.ResolverParentName + case "peers": + pkey = configuration.PeersParentName + case "rings": + pkey = configuration.RingParentName + case "defaults": + pkey = configuration.DefaultsParentName + } + key := fmt.Sprintf("%s/%s", pkey, pname) + if pname == "dynamic_update_rule_list" { + key = "dynamic_update" + } - ellist, ok := pmap[elementKey] + switch pmap := pmvalue.(type) { + case map[string]interface{}: + e, ok := pmap[elementKey] if !ok { - res[key] = *new(T) + res[key] = []T{} continue } - elistj, err := json.Marshal(ellist) + // Case list of T + var resources []T + elistj, err := json.Marshal(e) if err != nil { return err } - var resources T err = json.Unmarshal(elistj, &resources) + if err == nil { + res[key] = append(res[key], resources...) + } else { + // Case map[string]T + var resources map[string]T + err = json.Unmarshal(elistj, &resources) + if err != nil { + return err + } + for _, v := range resources { + res[key] = append(res[key], v) + } + } + case []interface{}: + // Case dynnamic update rule + var resources []T + elistj, err := json.Marshal(pmap) if err != nil { - return err + continue } - res[key] = resources + err = json.Unmarshal(elistj, &resources) + if err == nil { + res[key] = append(res[key], resources...) + } + default: + // fmt.Printf("pmap type not handled: %+v\n", pmap) + continue } - } else { - pmap := v.(map[string]interface{}) - key := parentKey - ellist, ok := pmap[elementKey] - if !ok { - res[key] = *new(T) - } - elistj, err := json.Marshal(ellist) - if err != nil { - return err - } - var resources T - err = json.Unmarshal(elistj, &resources) - if err != nil { - return err - } - res[key] = resources } return nil } +func expectedRootChildResources[P, T any](res map[string][]T, parentKey, parentNameKey, elementKey string) error { + v := expectedStructured[parentKey] + parentMap, ok := v.(map[string]interface{}) + if !ok { + return fmt.Errorf("expectedStructuredV3[%s] is not a map[string]interface{}", parentKey) + } + key := parentKey + + e, ok := parentMap[elementKey] + if !ok { + res[key] = []T{} + } + var resources []T + elistj, err := json.Marshal(e) + if err != nil { + return err + } + + err = json.Unmarshal(elistj, &resources) + if err == nil { + res[key] = append(res[key], resources...) + } + + return nil +} + +func toResMap[T any](keyRoot string, resources map[string]T) map[string][]*T { + res := make(map[string][]*T) + for _, v := range resources { + currentv := v + res[keyRoot] = append(res[keyRoot], ¤tv) + } + return res +} + +func toSliceOfPtrs[T any](resources []T) []*T { + res := make([]*T, 0) + for _, v := range resources { + currentv := v + res = append(res, ¤tv) + } + return res +} + func StructuredToBackendMap() map[string]models.Backends { - var l models.Backends - _ = expectedResources(&l, "backends") + resources, _ := expectedResources[models.Backend]("backends") res := make(map[string]models.Backends) keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToFrontendMap() map[string]models.Frontends { - var l models.Frontends - _ = expectedResources(&l, "frontends") + resources, _ := expectedResources[models.Frontend]("frontends") res := make(map[string]models.Frontends) keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToCacheMap() map[string]models.Caches { - var l models.Caches - _ = expectedResources(&l, "caches") + resources, _ := expectedResources[models.Cache]("caches") res := make(map[string]models.Caches) keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToACLMap() map[string]models.Acls { res := make(map[string]models.Acls) - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "acls") - _ = expectedChildResources[models.Backend](res, "backends", "name", "acls") - _ = expectedChildResources[models.FCGIApp](res, "fcgi_apps", "name", "acls") + resources := make(map[string][]models.ACL) + _ = expectedChildResources[models.Frontend, models.ACL](resources, "frontends", "name", "acl_list") + _ = expectedChildResources[models.Backend, models.ACL](resources, "backends", "name", "acl_list") + _ = expectedChildResources[models.FCGIApp, models.ACL](resources, "fcgi_apps", "name", "acl_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToBackendSwitchingRuleMap() map[string]models.BackendSwitchingRules { res := make(map[string]models.BackendSwitchingRules) - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "backend_switching_rules") + resources := make(map[string][]models.BackendSwitchingRule) + _ = expectedChildResources[models.Frontend, models.BackendSwitchingRule](resources, "frontends", "name", "backend_switching_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToBindMap() map[string]models.Binds { res := make(map[string]models.Binds) - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "binds") - _ = expectedChildResources[models.LogForward](res, "log_forwards", "name", "binds") - _ = expectedChildResources[models.PeerSection](res, "peers", "name", "binds") + resources := make(map[string][]models.Bind) + _ = expectedChildResources[models.Frontend, models.Bind](resources, "frontends", "name", "binds") + _ = expectedChildResources[models.LogForward, models.Bind](resources, "log_forwards", "name", "binds") + _ = expectedChildResources[models.PeerSection, models.Bind](resources, "peers", "name", "bindss") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToCaptureMap() map[string]models.Captures { res := make(map[string]models.Captures) - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "captures") + resources := make(map[string][]models.Capture) + _ = expectedChildResources[models.Frontend, models.Capture](resources, "frontends", "name", "captures") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToDefaultsMap() models.Defaults { - var l models.Defaults - _ = expectedResources(&l, "defaults") - return l + resources, _ := expectedResources[models.Defaults]("defaults") + return resources[""] } func StructuredToGlobalMap() models.Global { - var l models.Global - _ = expectedResources(&l, "global") - return l + resources, _ := expectedResources[models.Global]("global") + return resources[""] } func StructuredToNamedDefaultsMap() map[string][]*models.Defaults { - var l []*models.Defaults + resources, _ := expectedResources[models.Defaults]("named_defaults") res := make(map[string][]*models.Defaults) - _ = expectedResources(&l, "named_defaults") - for _, v := range l { - res[v.Name] = append(res[v.Name], v) + for _, v := range resources { + currentv := v + res[v.Name] = append(res[v.Name], ¤tv) } return res } func StructuredToDgramBindMap() map[string]models.DgramBinds { res := make(map[string]models.DgramBinds) - _ = expectedChildResources[models.LogForward](res, "log_forwards", "name", "dgram_binds") + resources := make(map[string][]models.DgramBind) + _ = expectedChildResources[models.LogForward, models.DgramBind](resources, "log_forwards", "name", "dgram_binds") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToFCGIAppMap() map[string]models.FCGIApps { + resources, _ := expectedResources[models.FCGIApp]("fcgi_apps") res := make(map[string]models.FCGIApps) - var l models.FCGIApps - _ = expectedResources(&l, "fcgi_apps") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToFilterMap() map[string]models.Filters { res := make(map[string]models.Filters) - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "filters") - _ = expectedChildResources[models.Backend](res, "backends", "name", "filters") + resources := make(map[string][]models.Filter) + _ = expectedChildResources[models.Frontend, models.Filter](resources, "frontends", "name", "filter_list") + _ = expectedChildResources[models.Backend, models.Filter](resources, "backends", "name", "filter_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToGroupMap() map[string]models.Groups { res := make(map[string]models.Groups) - _ = expectedChildResources[models.Userlist](res, "userlists", "name", "groups") + resources := make(map[string][]models.Group) + _ = expectedChildResources[models.Userlist, models.Group](resources, "userlists", "name", "groups") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToHTTPAfterResponseRuleMap() map[string]models.HTTPAfterResponseRules { res := make(map[string]models.HTTPAfterResponseRules) - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "http_after_response_rules") - _ = expectedChildResources[models.Backend](res, "backends", "name", "http_after_response_rules") + resources := make(map[string][]models.HTTPAfterResponseRule) + _ = expectedChildResources[models.Frontend, models.HTTPAfterResponseRule](resources, "frontends", "name", "http_after_response_rule_list") + _ = expectedChildResources[models.Backend, models.HTTPAfterResponseRule](resources, "backends", "name", "http_after_response_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToHTTPCheckMap() map[string]models.HTTPChecks { res := make(map[string]models.HTTPChecks) - _ = expectedChildResources[models.Backend](res, "backends", "name", "http_checks") - _ = expectedChildResources[models.Defaults](res, "defaults", "name", "http_checks") + resources := make(map[string][]models.HTTPCheck) + _ = expectedChildResources[models.Backend, models.HTTPCheck](resources, "backends", "name", "http_check_list") + _ = expectedRootChildResources[models.Defaults, models.HTTPCheck](resources, "defaults", "name", "http_check_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToHTTPErrorRuleMap() map[string]models.HTTPErrorRules { res := make(map[string]models.HTTPErrorRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "http_error_rules") - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "http_error_rules") - _ = expectedChildResources[models.Defaults](res, "defaults", "name", "http_error_rules") + resources := make(map[string][]models.HTTPErrorRule) + _ = expectedChildResources[models.Backend, models.HTTPErrorRule](resources, "backends", "name", "http_error_rule_list") + _ = expectedChildResources[models.Frontend, models.HTTPErrorRule](resources, "frontends", "name", "http_error_rule_list") + _ = expectedRootChildResources[models.Defaults, models.HTTPErrorRule](resources, "defaults", "name", "http_error_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToHTTPErrorSectionMap() map[string]models.HTTPErrorsSections { + resources, _ := expectedResources[models.HTTPErrorsSection]("http_errors") res := make(map[string]models.HTTPErrorsSections) - var l models.HTTPErrorsSections - _ = expectedResources(&l, "http_errors") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToHTTPRequestRuleMap() map[string]models.HTTPRequestRules { res := make(map[string]models.HTTPRequestRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "http_request_rules") - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "http_request_rules") + resources := make(map[string][]models.HTTPRequestRule) + _ = expectedChildResources[models.Backend, models.HTTPRequestRule](resources, "backends", "name", "http_request_rule_list") + _ = expectedChildResources[models.Frontend, models.HTTPRequestRule](resources, "frontends", "name", "http_request_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToHTTPResponseRuleMap() map[string]models.HTTPResponseRules { res := make(map[string]models.HTTPResponseRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "http_response_rules") - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "http_response_rules") + resources := make(map[string][]models.HTTPResponseRule) + _ = expectedChildResources[models.Backend, models.HTTPResponseRule](resources, "backends", "name", "http_response_rule_list") + _ = expectedChildResources[models.Frontend, models.HTTPResponseRule](resources, "frontends", "name", "http_response_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToLogForwardMap() map[string]models.LogForwards { + resources, _ := expectedResources[models.LogForward]("log_forwards") res := make(map[string]models.LogForwards) - var l models.LogForwards - _ = expectedResources(&l, "log_forwards") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToLogTargetMap() map[string]models.LogTargets { res := make(map[string]models.LogTargets) - _ = expectedChildResources[models.Backend](res, "backends", "name", "log_targets") - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "log_targets") - _ = expectedChildResources[models.LogForward](res, "log_forwards", "name", "log_targets") - _ = expectedChildResources[models.PeerSection](res, "peers", "name", "log_targets") - _ = expectedChildResources[models.Defaults](res, "defaults", "name", "log_targets") - _ = expectedChildResources[models.Global](res, "global", "name", "log_targets") + resources := make(map[string][]models.LogTarget) + _ = expectedChildResources[models.Backend, models.LogTarget](resources, "backends", "name", "log_target_list") + _ = expectedChildResources[models.Frontend, models.LogTarget](resources, "frontends", "name", "log_target_list") + _ = expectedChildResources[models.LogForward, models.LogTarget](resources, "log_forwards", "name", "log_target_list") + _ = expectedChildResources[models.PeerSection, models.LogTarget](resources, "peers", "name", "log_target_list") + _ = expectedRootChildResources[models.Defaults](resources, "defaults", "name", "log_target_list") + _ = expectedRootChildResources[models.Global](resources, "global", "name", "log_target_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToMailerEntryMap() map[string]models.MailerEntries { res := make(map[string]models.MailerEntries) - _ = expectedChildResources[models.MailersSection](res, "mailers_sections", "name", "mailer_entries") + resources := make(map[string][]models.MailerEntry) + _ = expectedChildResources[models.MailersSection, models.MailerEntry](resources, "mailers_sections", "name", "mailer_entries") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToMailersSectionMap() map[string]models.MailersSections { + resources, _ := expectedResources[models.MailersSection]("mailers_sections") res := make(map[string]models.MailersSections) - var l models.MailersSections - _ = expectedResources(&l, "mailers_sections") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToNameserverMap() map[string]models.Nameservers { res := make(map[string]models.Nameservers) - _ = expectedChildResources[models.Resolver](res, "resolvers", "name", "nameservers") + resources := make(map[string][]models.Nameserver) + _ = expectedChildResources[models.Resolver, models.Nameserver](resources, "resolvers", "name", "nameservers") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToPeerEntryMap() map[string]models.PeerEntries { res := make(map[string]models.PeerEntries) - _ = expectedChildResources[models.PeerSection](res, "peers", "name", "peer_entries") + resources := make(map[string][]models.PeerEntry) + _ = expectedChildResources[models.PeerSection, models.PeerEntry](resources, "peers", "name", "peer_entries") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToPeerSectionMap() map[string]models.PeerSections { + resources, _ := expectedResources[models.PeerSection]("peers") res := make(map[string]models.PeerSections) - var l models.PeerSections - _ = expectedResources(&l, "peers") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToProgramMap() map[string]models.Programs { + resources, _ := expectedResources[models.Program]("programs") res := make(map[string]models.Programs) - var l models.Programs - _ = expectedResources(&l, "programs") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToResolverMap() map[string]models.Resolvers { + resources, _ := expectedResources[models.Resolver]("resolvers") res := make(map[string]models.Resolvers) - var l models.Resolvers - _ = expectedResources(&l, "resolvers") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToRingMap() map[string]models.Rings { + resources, _ := expectedResources[models.Ring]("rings") res := make(map[string]models.Rings) - var l models.Rings - _ = expectedResources(&l, "rings") keyRoot := "" - res[keyRoot] = l + t := toResMap(keyRoot, resources) + res[keyRoot] = t[keyRoot] return res } func StructuredToServerSwitchingRuleMap() map[string]models.ServerSwitchingRules { res := make(map[string]models.ServerSwitchingRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "server_switching_rules") + resources := make(map[string][]models.ServerSwitchingRule) + _ = expectedChildResources[models.Backend](resources, "backends", "name", "server_switching_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToServerTemplateMap() map[string]models.ServerTemplates { res := make(map[string]models.ServerTemplates) - _ = expectedChildResources[models.Backend](res, "backends", "name", "server_templates") - + resources := make(map[string][]models.ServerTemplate) + _ = expectedChildResources[models.Backend, models.ServerTemplate](resources, "backends", "name", "server_templates") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToServerMap() map[string]models.Servers { //nolint:dupl res := make(map[string]models.Servers) - _ = expectedChildResources[models.Backend](res, "backends", "name", "servers") - _ = expectedChildResources[models.Ring](res, "rings", "name", "servers") - _ = expectedChildResources[models.PeerSection](res, "peers", "name", "servers") + resources := make(map[string][]models.Server) + _ = expectedChildResources[models.Backend, models.Server](resources, "backends", "name", "servers") + _ = expectedChildResources[models.Ring, models.Server](resources, "rings", "name", "servers") + _ = expectedChildResources[models.PeerSection, models.Server](resources, "peers", "name", "servers") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToStickRuleMap() map[string]models.StickRules { res := make(map[string]models.StickRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "stick_rules") + resources := make(map[string][]models.StickRule) + _ = expectedChildResources[models.Backend, models.StickRule](resources, "backends", "name", "stick_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToTCPRequestRuleMap() map[string]models.TCPRequestRules { res := make(map[string]models.TCPRequestRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "tcp_request_rules") - _ = expectedChildResources[models.Frontend](res, "frontends", "name", "tcp_request_rules") + resources := make(map[string][]models.TCPRequestRule) + _ = expectedChildResources[models.Backend, models.TCPRequestRule](resources, "backends", "name", "tcp_request_rule_list") + _ = expectedChildResources[models.Frontend](resources, "frontends", "name", "tcp_request_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToTCPResponseRuleMap() map[string]models.TCPResponseRules { res := make(map[string]models.TCPResponseRules) - _ = expectedChildResources[models.Backend](res, "backends", "name", "tcp_response_rules") + resources := make(map[string][]models.TCPResponseRule) + _ = expectedChildResources[models.Backend](resources, "backends", "name", "tcp_response_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToTCPCheckMap() map[string]models.TCPChecks { res := make(map[string]models.TCPChecks) - _ = expectedChildResources[models.Backend](res, "backends", "name", "tcp_checks") - _ = expectedChildResources[models.Defaults](res, "defaults", "name", "tcp_checks") + resources := make(map[string][]models.TCPCheck) + _ = expectedChildResources[models.Backend, models.TCPCheck](resources, "backends", "name", "tcp_check_rule_list") + _ = expectedRootChildResources[models.Defaults, models.TCPCheck](resources, "defaults", "name", "tcp_check_rule_list") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } func StructuredToUserMap() map[string]models.Users { res := make(map[string]models.Users) - _ = expectedChildResources[models.Defaults](res, "userlists", "name", "users") + resources := make(map[string][]models.User) + _ = expectedChildResources[models.Defaults, models.User](resources, "userlists", "name", "users") + for k, v := range resources { + res[k] = toSliceOfPtrs(v) + } return res } diff --git a/test/expected/structured.json b/test/expected/structured.json index 8d676a7a..b0e422cc 100644 --- a/test/expected/structured.json +++ b/test/expected/structured.json @@ -132,7 +132,7 @@ "tcp_smart_connect": "enabled", "tcpka": "enabled", "transparent": "enabled", - "http_checks": [ + "http_check_list": [ { "index": 0, "type": "send-state" @@ -142,7 +142,7 @@ "type": "disable-on-404" } ], - "http_error_rules": [ + "http_error_rule_list": [ { "index": 0, "return_content": "/test/503", @@ -160,7 +160,7 @@ "type": "status" } ], - "tcp_check_rules": [ + "tcp_check_rule_list": [ { "action": "send", "data": "GET\\ /\\ HTTP/2.0\\r\\n", @@ -498,7 +498,7 @@ "patch_file": "path1,path2" }, "zero_warning": true, - "log_targets": [ + "log_target_list": [ { "address": "127.0.0.1:10001", "facility": "local0", @@ -515,8 +515,8 @@ } ] }, - "backends": [ - { + "backends": { + "test": { "error_files": [ { "code": 403, @@ -698,7 +698,7 @@ "transparent": "enabled", "tunnel_timeout": 5000, "use_fcgi_app": "app-name", - "http_checks": [ + "http_check_list": [ { "index": 0, "type": "connect" @@ -790,7 +790,7 @@ "type": "disable-on-404" } ], - "http_request_rules": [ + "http_request_rule_list": [ { "expr": "hdr(x-dst)", "index": 0, @@ -802,7 +802,7 @@ "type": "set-dst-port" } ], - "server_switching_rules": [ + "server_switching_rule_list": [ { "cond": "if", "cond_test": "TRUE", @@ -816,87 +816,7 @@ "target_server": "webserv2" } ], - "server_templates": [ - { - "check": "enabled", - "fqdn": "google.com", - "num_or_range": "1-3", - "port": 80, - "prefix": "srv" - }, - { - "backup": "enabled", - "check": "enabled", - "fqdn": "google.com", - "num_or_range": "1-10", - "port": 8080, - "prefix": "site" - }, - { - "backup": "disabled", - "check": "enabled", - "fqdn": "google.com", - "num_or_range": "10-100", - "port": 443, - "prefix": "website" - }, - { - "backup": "enabled", - "check": "enabled", - "fqdn": "test.com", - "num_or_range": "5", - "port": 0, - "prefix": "test" - } - ], - "servers": [ - { - "client_sigalgs": "ECDSA+SHA256:RSA+SHA256", - "cookie": "BLAH", - "curves": "secp384r1", - "inter": 2000, - "log-bufsize": 10, - "maxconn": 1000, - "pool_low_conn": 128, - "pool_purge_delay": 10000, - "proxy-v2-options": [ - "authority", - "crc32c" - ], - "set-proxy-v2-tlv-fmt": { - "id": "0x20", - "value": "%[fc_pp_tlv(0x20)]" - }, - "sigalgs": "ECDSA+SHA256", - "slowstart": 6000, - "ssl": "enabled", - "tcp_ut": 2000, - "weight": 10, - "ws": "h1", - "address": "192.168.1.1", - "id": 1234, - "name": "webserv", - "port": 9200 - }, - { - "cookie": "BLAH", - "inter": 2000, - "maxconn": 1000, - "pool_low_conn": 128, - "proxy-v2-options": [ - "authority", - "crc32c" - ], - "slowstart": 6000, - "ssl": "enabled", - "weight": 10, - "ws": "h1", - "address": "192.168.1.1", - "name": "webserv2", - "port": 9300 - } - ], - "stick_rules": [ + "stick_rule_list": [ { "index": 0, "pattern": "src", @@ -935,7 +855,7 @@ "type": "store-response" } ], - "tcp_response_rules": [ + "tcp_response_rule_list": [ { "action": "accept", "cond": "if", @@ -1082,9 +1002,101 @@ "var_name": "my_var", "var_scope": "req" } - ] + ], + "waf_body_rule_list": [ + { + "index": 0, + "type": "allow" + }, + { + "cond": "if", + "cond_test": "{ var(txn.myip) -m ip 127.0.0.0/8 10.0.0.0/8 }", + "index": 1, + "type": "deny" + } + ], + "server_templates": { + "site": { + "backup": "enabled", + "check": "enabled", + "fqdn": "google.com", + "num_or_range": "1-10", + "port": 8080, + "prefix": "site" + }, + "srv": { + "check": "enabled", + "fqdn": "google.com", + "num_or_range": "1-3", + "port": 80, + "prefix": "srv" + }, + "test": { + "backup": "enabled", + "check": "enabled", + "fqdn": "test.com", + "num_or_range": "5", + "port": 0, + "prefix": "test" + }, + "website": { + "backup": "disabled", + "check": "enabled", + "fqdn": "google.com", + "num_or_range": "10-100", + "port": 443, + "prefix": "website" + } + }, + "servers": { + "webserv": { + "client_sigalgs": "ECDSA+SHA256:RSA+SHA256", + "cookie": "BLAH", + "curves": "secp384r1", + "inter": 2000, + "log-bufsize": 10, + "maxconn": 1000, + "pool_low_conn": 128, + "pool_purge_delay": 10000, + "proxy-v2-options": [ + "authority", + "crc32c" + ], + "set-proxy-v2-tlv-fmt": { + "id": "0x20", + "value": "%[fc_pp_tlv(0x20)]" + }, + "sigalgs": "ECDSA+SHA256", + "slowstart": 6000, + "ssl": "enabled", + "tcp_ut": 2000, + "weight": 10, + "ws": "h1", + "address": "192.168.1.1", + "id": 1234, + "name": "webserv", + "port": 9200 + }, + "webserv2": { + "cookie": "BLAH", + "inter": 2000, + "maxconn": 1000, + "pool_low_conn": 128, + "proxy-v2-options": [ + "authority", + "crc32c" + ], + "slowstart": 6000, + "ssl": "enabled", + "weight": 10, + "ws": "h1", + "address": "192.168.1.1", + "name": "webserv2", + "port": 9300 + } + } }, - { + "test_2": { "adv_check": "httpchk", "balance": { "algorithm": "roundrobin" @@ -1194,7 +1206,7 @@ "tcpka": "enabled", "transparent": "disabled", "tunnel_timeout": 5000, - "http_checks": [ + "http_check_list": [ { "index": 0, "match": "rstatus", @@ -1202,7 +1214,7 @@ "type": "expect" } ], - "http_error_rules": [ + "http_error_rule_list": [ { "return_hdrs": [ { @@ -1237,9 +1249,9 @@ } ] } - ], - "caches": [ - { + }, + "caches": { + "test": { "max_age": 60, "max_object_size": 8, "max_secondary_entries": 10, @@ -1247,9 +1259,9 @@ "process_vary": true, "total_max_size": 1024 } - ], - "fcgi_apps": [ - { + }, + "fcgi_apps": { + "test": { "docroot": "/path/to/chroot", "get_values": "enabled", "index": "index.php", @@ -1323,7 +1335,7 @@ "name": "name" } ], - "acls": [ + "acl_list": [ { "acl_name": "invalid_src", "criterion": "src", @@ -1344,7 +1356,7 @@ } ] }, - { + "test_2": { "docroot": "/path/to/chroot", "get_values": "enabled", "index": "index.php", @@ -1418,7 +1430,7 @@ "name": "name" } ], - "acls": [ + "acl_list": [ { "acl_name": "invalid_src", "criterion": "src", @@ -1439,90 +1451,9 @@ } ] } - ], - "frontends": [ - { - "backlog": 2048, - "bind_process": "even", - "client_timeout": 4000, - "clitcpka": "enabled", - "clitcpka_cnt": 10, - "clitcpka_idle": 10000, - "clitcpka_intvl": 10000, - "contstats": "enabled", - "default_backend": "test_2", - "dontlog_normal": "disabled", - "dontlognull": "enabled", - "from": "test_defaults", - "http_connection_mode": "httpclose", - "http_ignore_probes": "disabled", - "http_keep_alive_timeout": 3000, - "http_no_delay": "disabled", - "http_request_timeout": 2000, - "http_use_proxy_header": "disabled", - "httplog": true, - "httpslog": "disabled", - "idle_close_on_response": "disabled", - "independent_streams": "disabled", - "log_separate_errors": "enabled", - "log_tag": "bla", - "maxconn": 2000, - "mode": "http", - "name": "test_2", - "nolinger": "disabled", - "originalto": { - "enabled": "enabled", - "except": "127.0.0.1", - "header": "X-Client-Dst" - }, - "socket_stats": "disabled", - "splice_auto": "disabled", - "splice_request": "disabled", - "splice_response": "disabled", - "stats_options": { - "stats_auths": [ - { - "passwd": "AdMiN123", - "user": "admin" - }, - { - "passwd": "AdMiN1234", - "user": "admin2" - } - ], - "stats_realm": true, - "stats_realm_realm": "HAProxy\\\\ Statistics", - "stats_show_modules": true, - "stats_show_node_name": null - }, - "tcp_smart_accept": "disabled", - "tcpka": "enabled", - "unique_id_format": "%{+X}o%ci:%cp_%fi:%fp_%Ts_%rt", - "unique_id_header": "X-Unique-ID-test-2", - "http_request_rules": [ - { - "capture_len": 10, - "capture_sample": "req.cook_cnt(FirstVisit),bool", - "index": 0, - "type": "capture" - }, - { - "capture_id": 0, - "capture_sample": "req.cook_cnt(FirstVisit),bool", - "index": 1, - "type": "capture" - } - ], - "http_response_rules": [ - { - "capture_id": 0, - "capture_sample": "res.header", - "index": 0, - "type": "capture" - } - ] - }, - { + }, + "frontends": { + "test": { "error_files": [ { "code": 403, @@ -1633,7 +1564,7 @@ "tcpka": "enabled", "unique_id_format": "%{+X}o%ci:%cp_%fi:%fp_%Ts_%rt:%pid", "unique_id_header": "X-Unique-ID", - "acls": [ + "acl_list": [ { "acl_name": "invalid_src", "criterion": "src", @@ -1658,7 +1589,7 @@ "index": 3 } ], - "backend_switching_rules": [ + "backend_switching_rule_list": [ { "cond": "if", "cond_test": "TRUE", @@ -1670,58 +1601,14 @@ "name": "%[req.cookie(foo)]" } ], - "binds": [ - { - "ca_verify_file": "ca.pem", - "client_sigalgs": "ECDSA+SHA256:RSA+SHA256", - "name": "webserv", - "nice": 789, - "sigalgs": "RSA+SHA256", - "thread": "all", - "address": "192.168.1.1", - "port": 80 - }, - { - "name": "webserv2", - "thread": "1/all", - "address": "192.168.1.1", - "port": 8080 - }, - { - "name": "webserv3", - "thread": "1/1", - "address": "192.168.1.2", - "port": 8080 - }, - { - "name": "ipv6", - "thread": "1/1-1", - "address": "2a01:c9c0:a3:8::3", - "port": 80 - }, - { - "name": "test-quic", - "quic-socket": "connection", - "thread": "1/1", - "address": "192.168.1.1", - "port": 80 - }, - { - "name": "testnbcon", - "nbconn": 6, - "thread": "1/all", - "address": "192.168.1.1", - "port": 80 - } - ], - "captures": [ + "capture_list": [ { "index": 0, "length": 1, "type": "request" } ], - "filters": [ + "filter_list": [ { "index": 0, "rule-files": null, @@ -1768,7 +1655,7 @@ "type": "bwlim-out" } ], - "http_after_response_rules": [ + "http_after_response_rule_list": [ { "index": 0, "map_file": "map.lst", @@ -1881,7 +1768,7 @@ "var_scope": "sess" } ], - "http_error_rules": [ + "http_error_rule_list": [ { "index": 0, "return_content": "/var/errors.file", @@ -1891,7 +1778,7 @@ "type": "status" } ], - "http_request_rules": [ + "http_request_rule_list": [ { "cond": "if", "cond_test": "src 192.168.0.0/16", @@ -2241,7 +2128,7 @@ "type": "track-sc" } ], - "http_response_rules": [ + "http_response_rule_list": [ { "cond": "if", "cond_test": "src 192.168.0.0/16", @@ -2502,7 +2389,7 @@ "type": "set-timeout" } ], - "log_targets": [ + "log_target_list": [ { "global": true, "index": 0 @@ -2519,7 +2406,7 @@ "minlevel": "notice" } ], - "tcp_request_rules": [ + "tcp_request_rule_list": [ { "action": "accept", "cond": "if", @@ -2796,67 +2683,185 @@ "server_name": "srv3", "type": "session" } + ], + "binds": { + "ipv6": { + "name": "ipv6", + "thread": "1/1-1", + "address": "2a01:c9c0:a3:8::3", + "port": 80 + }, + "test-quic": { + "name": "test-quic", + "quic-socket": "connection", + "thread": "1/1", + "address": "192.168.1.1", + "port": 80 + }, + "testnbcon": { + "name": "testnbcon", + "nbconn": 6, + "thread": "1/all", + "address": "192.168.1.1", + "port": 80 + }, + "webserv": { + "ca_verify_file": "ca.pem", + "client_sigalgs": "ECDSA+SHA256:RSA+SHA256", + "name": "webserv", + "nice": 789, + "sigalgs": "RSA+SHA256", + "thread": "all", + "address": "192.168.1.1", + "port": 80 + }, + "webserv2": { + "name": "webserv2", + "thread": "1/all", + "address": "192.168.1.1", + "port": 8080 + }, + "webserv3": { + "name": "webserv3", + "thread": "1/1", + "address": "192.168.1.2", + "port": 8080 + } + } + }, + "test_2": { + "backlog": 2048, + "bind_process": "even", + "client_timeout": 4000, + "clitcpka": "enabled", + "clitcpka_cnt": 10, + "clitcpka_idle": 10000, + "clitcpka_intvl": 10000, + "contstats": "enabled", + "default_backend": "test_2", + "dontlog_normal": "disabled", + "dontlognull": "enabled", + "from": "test_defaults", + "http_connection_mode": "httpclose", + "http_ignore_probes": "disabled", + "http_keep_alive_timeout": 3000, + "http_no_delay": "disabled", + "http_request_timeout": 2000, + "http_use_proxy_header": "disabled", + "httplog": true, + "httpslog": "disabled", + "idle_close_on_response": "disabled", + "independent_streams": "disabled", + "log_separate_errors": "enabled", + "log_tag": "bla", + "maxconn": 2000, + "mode": "http", + "name": "test_2", + "nolinger": "disabled", + "originalto": { + "enabled": "enabled", + "except": "127.0.0.1", + "header": "X-Client-Dst" + }, + "socket_stats": "disabled", + "splice_auto": "disabled", + "splice_request": "disabled", + "splice_response": "disabled", + "stats_options": { + "stats_auths": [ + { + "passwd": "AdMiN123", + "user": "admin" + }, + { + "passwd": "AdMiN1234", + "user": "admin2" + } + ], + "stats_realm": true, + "stats_realm_realm": "HAProxy\\\\ Statistics", + "stats_show_modules": true, + "stats_show_node_name": null + }, + "tcp_smart_accept": "disabled", + "tcpka": "enabled", + "unique_id_format": "%{+X}o%ci:%cp_%fi:%fp_%Ts_%rt", + "unique_id_header": "X-Unique-ID-test-2", + "http_request_rule_list": [ + { + "capture_len": 10, + "capture_sample": "req.cook_cnt(FirstVisit),bool", + "index": 0, + "type": "capture" + }, + { + "capture_id": 0, + "capture_sample": "req.cook_cnt(FirstVisit),bool", + "index": 1, + "type": "capture" + } + ], + "http_response_rule_list": [ + { + "capture_id": 0, + "capture_sample": "res.header", + "index": 0, + "type": "capture" + } + ], + "waf_body_rule_list": [ + { + "cond": "unless", + "cond_test": "{ var(txn.jwt_alg) \"RS256\" }", + "index": 0, + "type": "deny" + } ] } - ], - "http_errors": [ - { + }, + "http_errors": { + "website-1": { "error_files": [ { "code": 400, - "file": "/etc/haproxy/errorfiles/site2/400.http" + "file": "/etc/haproxy/errorfiles/site1/400.http" }, { "code": 404, - "file": "/etc/haproxy/errorfiles/site2/404.http" + "file": "/etc/haproxy/errorfiles/site1/404.http" }, { - "code": 501, - "file": "/etc/haproxy/errorfiles/site2/501.http" + "code": 408, + "file": "/dev/null" } ], - "name": "website-2" + "name": "website-1" }, - { + "website-2": { "error_files": [ { "code": 400, - "file": "/etc/haproxy/errorfiles/site1/400.http" + "file": "/etc/haproxy/errorfiles/site2/400.http" }, { "code": 404, - "file": "/etc/haproxy/errorfiles/site1/404.http" + "file": "/etc/haproxy/errorfiles/site2/404.http" }, { - "code": 408, - "file": "/dev/null" + "code": 501, + "file": "/etc/haproxy/errorfiles/site2/501.http" } ], - "name": "website-1" + "name": "website-2" } - ], - "log_forwards": [ - { + }, + "log_forwards": { + "sylog-loadb": { "backlog": 10, "maxconn": 1000, "name": "sylog-loadb", "timeout_client": 10000, - "binds": [ - { - "name": "127.0.0.1:1514", - "address": "127.0.0.1", - "port": 1514 - } - ], - "dgram_binds": [ - { - "address": "127.0.0.1", - "name": "webserv", - "port": 1514, - "transparent": true - } - ], - "log_targets": [ + "log_target_list": [ { "global": true, "index": 0 @@ -2894,29 +2899,60 @@ "sample_range": "4", "sample_size": 4 } - ] + ], + "binds": { + "127.0.0.1:1514": { + "name": "127.0.0.1:1514", + "address": "127.0.0.1", + "port": 1514 + } + }, + "dgram_binds": { + "webserv": { + "address": "127.0.0.1", + "name": "webserv", + "port": 1514, + "transparent": true + } + } } - ], - "mailers_sections": [ - { + }, + "mailers_sections": { + "localmailer1": { "name": "localmailer1", "timeout": 15000, - "mailer_entries": [ - { + "mailer_entries": { + "smtp1": { "address": "10.0.10.1", "name": "smtp1", "port": 514 }, - { + "smtp2": { "address": "10.0.10.2", "name": "smtp2", "port": 514 } - ] + } } - ], - "named_defaults": [ - { + }, + "named_defaults": { + "test_defaults": { + "backlog": 1024, + "balance": { + "algorithm": "roundrobin" + }, + "bind_process": "1-4", + "maxconn": 2000, + "mode": "http", + "name": "test_defaults" + }, + "test_defaults_2": { + "clitcpka": "enabled", + "from": "test_defaults", + "name": "test_defaults_2", + "srvtcpka": "enabled" + }, + "unnamed_defaults_1": { "error_files": [ { "code": 403, @@ -3050,7 +3086,7 @@ "tcp_smart_connect": "enabled", "tcpka": "enabled", "transparent": "enabled", - "http_checks": [ + "http_check_list": [ { "index": 0, "type": "send-state" @@ -3060,7 +3096,7 @@ "type": "disable-on-404" } ], - "http_error_rules": [ + "http_error_rule_list": [ { "index": 0, "return_content": "/test/503", @@ -3078,33 +3114,17 @@ "type": "status" } ], - "tcp_check_rules": [ + "tcp_check_rule_list": [ { "action": "send", "data": "GET\\ /\\ HTTP/2.0\\r\\n", "index": 0 } ] - }, - { - "backlog": 1024, - "balance": { - "algorithm": "roundrobin" - }, - "bind_process": "1-4", - "maxconn": 2000, - "mode": "http", - "name": "test_defaults" - }, - { - "clitcpka": "enabled", - "from": "test_defaults", - "name": "test_defaults_2", - "srvtcpka": "enabled" } - ], - "peers": [ - { + }, + "peers": { + "mycluster": { "default_bind": { "alpn": "h2,http/1.1", "ssl": true, @@ -3121,37 +3141,37 @@ "enabled": true, "name": "mycluster", "shards": 3, - "peer_entries": [ - { + "peer_entries": { + "aggregator": { + "address": "HARDCODEDCLUSTERIP", + "name": "aggregator", + "port": 10023 + }, + "hapee": { "address": "192.168.1.1", "name": "hapee", "port": 1023, "shard": 1 - }, - { - "address": "HARDCODEDCLUSTERIP", - "name": "aggregator", - "port": 10023 } - ] + } } - ], - "programs": [ - { + }, + "programs": { + "test": { "command": "echo \"Hello, World!\"", "group": "hapee", "name": "test", "start-on-reload": "enabled", "user": "hapee-lb" }, - { + "test_2": { "command": "echo \"Hello, World!\"", "name": "test_2", "start-on-reload": "disabled" } - ], - "resolvers": [ - { + }, + "resolvers": { + "test": { "accepted_payload_size": 8192, "hold_nx": 30000, "hold_other": 30000, @@ -3162,17 +3182,17 @@ "resolve_retries": 3, "timeout_resolve": 1000, "timeout_retry": 1000, - "nameservers": [ - { + "nameservers": { + "dns1": { "address": "10.0.0.1", "name": "dns1", "port": 53 } - ] + } } - ], - "rings": [ - { + }, + "rings": { + "myring": { "description": "\"My local buffer\"", "format": "rfc3164", "maxlen": 1200, @@ -3180,14 +3200,14 @@ "size": 32764, "timeout_connect": 5000, "timeout_server": 10000, - "servers": [ - { + "servers": { + "mysyslogsrv": { "log_proto": "octet-count", "address": "127.0.0.1", "name": "mysyslogsrv", "port": 6514 }, - { + "s1": { "check": "enabled", "resolve-net": "10.0.0.0/8,10.200.200.0/12", "resolve_opts": "allow-dup-ip,ignore-weight", @@ -3195,68 +3215,68 @@ "name": "s1", "port": 80 } - ] + } } - ], - "userlists": [ - { + }, + "userlists": { + "first": { "name": "first", - "groups": [ - { + "groups": { + "G1": { "name": "G1", "users": "tiger,scott" }, - { + "G2": { "name": "G2", "users": "scott" } - ], - "users": [ - { - "password": "$6$k6y3o.eP$JlKBx9za9667qe4xHSwRv6J.C0/D7cV91", - "secure_password": true, - "username": "tiger" - }, - { + }, + "users": { + "scott": { "password": "elgato", "secure_password": false, "username": "scott" + }, + "tiger": { + "password": "$6$k6y3o.eP$JlKBx9za9667qe4xHSwRv6J.C0/D7cV91", + "secure_password": true, + "username": "tiger" } - ] + } }, - { + "second": { "name": "second", - "groups": [ - { + "groups": { + "one": { "name": "one" }, - { - "name": "two" - }, - { + "three": { "name": "three" + }, + "two": { + "name": "two" } - ], - "users": [ - { + }, + "users": { + "anderson": { + "groups": "two", + "password": "hello", + "secure_password": false, + "username": "anderson" + }, + "neo": { "groups": "one", "password": "$6$k6y3o.eP$JlKBxxHSwRv6J.C0/D7cV91", "secure_password": true, "username": "neo" }, - { + "thomas": { "groups": "one,two", "password": "white-rabbit", "secure_password": false, "username": "thomas" - }, - { - "groups": "two", - "password": "hello", - "secure_password": false, - "username": "anderson" } - ] + } } - ] + } }