Skip to content

Commit

Permalink
Fix backend init (#524)
Browse files Browse the repository at this point in the history
* Fix missing ptr receiver

* move sequence resolving to config package

* catch index 0 panic and return a context error

* Fix backend configuration order for oauth2 refs

* refactor sequences to own pkg

* add changelog

* Apply suggestions from code review

Co-authored-by: Johannes Koch <53434855+johakoch@users.noreply.github.com>

* private method

Co-authored-by: Johannes Koch <53434855+johakoch@users.noreply.github.com>
  • Loading branch information
Marcel Ludwig and johakoch authored Jun 21, 2022
1 parent 81e563f commit ff40871
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 63 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

Unreleased changes are available as `avenga/couper:edge` container.

* **Fixed**
* configuration related panic while loading backends with `oauth2` block which depends on other defined backends ([#524](https://github.com/avenga/couper/pull/524))

---

## [1.9.1](https://github.com/avenga/couper/releases/tag/v1.9.1)
Expand Down
10 changes: 6 additions & 4 deletions config/configload/endpoint_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (

"github.com/avenga/couper/config"
"github.com/avenga/couper/config/body"
"github.com/avenga/couper/config/sequence"
"github.com/avenga/couper/eval"
)

// buildSequences collects possible dependencies from 'backend_responses' variable.
func buildSequences(names map[string]hcl.Body, endpoint *config.Endpoint) (err error) {
sequences := map[string]*config.Sequence{}
sequences := map[string]*sequence.Item{}

defer func() {
if rc := recover(); rc != nil {
Expand All @@ -35,14 +37,14 @@ func buildSequences(names map[string]hcl.Body, endpoint *config.Endpoint) (err e

seq, exist := sequences[name]
if !exist {
seq = &config.Sequence{Name: name, BodyRange: getRange(b)}
seq = &sequence.Item{Name: name, BodyRange: getRange(b)}
sequences[name] = seq
}

for _, r := range refs {
ref, ok := sequences[r]
if !ok {
ref = &config.Sequence{Name: r, BodyRange: getRange(b)}
ref = &sequence.Item{Name: r, BodyRange: getRange(b)}
sequences[r] = ref
}
// Do not add ourselves
Expand All @@ -68,7 +70,7 @@ func responseReferences(b hcl.Body) []string {

for _, expr := range body.CollectExpressions(b) {
for _, traversal := range expr.Variables() {
if traversal.RootName() != "backend_responses" || len(traversal) < 2 {
if traversal.RootName() != eval.BackendResponses || len(traversal) < 2 {
continue
}

Expand Down
109 changes: 107 additions & 2 deletions config/configload/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"

"github.com/avenga/couper/config"
"github.com/avenga/couper/config/sequence"
"github.com/avenga/couper/errors"
"github.com/avenga/couper/eval"
)

Expand Down Expand Up @@ -69,7 +72,13 @@ func (h *helper) addBackend(block *hcl.Block) error {
}

func (h *helper) configureDefinedBackends() error {
for name, b := range h.defsBackends {
backendNames, err := h.resolveBackendDeps()
if err != nil {
return err
}

for _, name := range backendNames {
b := h.defsBackends[name]
be, err := PrepareBackend(h, "_init", "", &config.Backend{Name: name, Remain: b})
if err != nil {
return err
Expand All @@ -81,7 +90,7 @@ func (h *helper) configureDefinedBackends() error {

h.defsBackends[name] = be
}
return nil
return err
}

func (h *helper) configureACBackends() error {
Expand All @@ -106,3 +115,99 @@ func (h *helper) configureACBackends() error {
}
return nil
}

// resolveBackendDeps returns defined backends ordered by reference. Referenced ones need to be configured first.
func (h *helper) resolveBackendDeps() (uniqueItems []string, err error) {
// collect referenced backends
refs := make(map[string][]string)
h.collectBackendDeps(refs)
// built up deps
refPtr := map[string]*sequence.Item{}
for name := range refs {
parent := &sequence.Item{Name: name}
refPtr[name] = parent
}

defer func() {
if p := recover(); p != nil { // since we use sequence related logic, replace wording due to backend context here
err = errors.Configuration.Message(strings.Replace(fmt.Sprintf("%s", p), "sequence ", "", 1))
}
}()

var defs sequence.List
for parent, ref := range refs {
for _, r := range ref {
p, _ := refPtr[parent]
if be, exist := refPtr[r]; exist {
p.Add(be)
} else {
p.Add(&sequence.Item{Name: r})
}
defs = append(defs, p)
}
}

items := sequence.Dependencies(defs)

// do not forget the other ones
var standalone []string
for def, _ := range h.defsBackends {
standalone = append(standalone, def)
}
items = append(items, standalone)

// unique by name /w score (sort?) // TODO: MAY refine with scoring of appearance
unique := make(map[string]int)
for _, seqItem := range items {
for _, name := range seqItem {
if _, exist := unique[name]; !exist {
unique[name] = 1
uniqueItems = append(uniqueItems, name)
} else {
unique[name]++
}
}
}

return uniqueItems, err
}

func (h *helper) collectBackendDeps(refs map[string][]string) {
for name, b := range h.defsBackends {
refs[name] = nil
content, _, _ := b.PartialContent(&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{{Type: oauth2}}},
)
oaBlocks := content.Blocks.OfType(oauth2)
for _, ob := range oaBlocks {
osb, ok := ob.Body.(*hclsyntax.Body)
if !ok {
continue
}

for _, be := range osb.Attributes {
if be.Name == backend {
val, _ := be.Expr.Value(envContext)
refs[name] = append(refs[name], val.AsString())
break
}
}

for _, block := range osb.Blocks {
if block.Type != backend {
continue
}
if len(block.Labels) > 0 {
refs[name] = append(refs[name], block.Labels[0])
}

for _, subBlock := range block.Body.Blocks {
if subBlock.Type == oauth2 {
h.collectBackendDeps(refs)
break
}
}
}
}
}
}
3 changes: 2 additions & 1 deletion config/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/hashicorp/hcl/v2/gohcl"

"github.com/avenga/couper/config/meta"
"github.com/avenga/couper/config/sequence"
)

var _ Inline = &Endpoint{}
Expand All @@ -25,7 +26,7 @@ type Endpoint struct {
Proxies Proxies
Requests Requests
RequiredPermission hcl.Expression
Sequences Sequences
Sequences sequence.List
}

// Endpoints represents a list of <Endpoint> objects.
Expand Down
18 changes: 9 additions & 9 deletions config/oauth2ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@ type OAuth2ReqAuth struct {
}

// Reference implements the <BackendReference> interface.
func (oa OAuth2ReqAuth) Reference() string {
func (oa *OAuth2ReqAuth) Reference() string {
return oa.BackendName
}

// HCLBody implements the <Inline> interface.
func (oa OAuth2ReqAuth) HCLBody() hcl.Body {
func (oa *OAuth2ReqAuth) HCLBody() hcl.Body {
return oa.Remain
}

// Inline implements the <Inline> interface.
func (oa OAuth2ReqAuth) Inline() interface{} {
func (oa *OAuth2ReqAuth) Inline() interface{} {
type Inline struct {
Backend *Backend `hcl:"backend,block"`
}
Expand All @@ -55,7 +55,7 @@ func (oa OAuth2ReqAuth) Inline() interface{} {
}

// Schema implements the <Inline> interface.
func (oa OAuth2ReqAuth) Schema(inline bool) *hcl.BodySchema {
func (oa *OAuth2ReqAuth) Schema(inline bool) *hcl.BodySchema {
if !inline {
schema, _ := gohcl.ImpliedBodySchema(oa)
return schema
Expand All @@ -78,25 +78,25 @@ func SchemaWithOAuth2RA(schema *hcl.BodySchema) *hcl.BodySchema {
return schema
}

func (oa OAuth2ReqAuth) GetClientID() string {
func (oa *OAuth2ReqAuth) GetClientID() string {
return oa.ClientID
}

func (oa OAuth2ReqAuth) GetClientSecret() string {
func (oa *OAuth2ReqAuth) GetClientSecret() string {
return oa.ClientSecret
}

func (oa OAuth2ReqAuth) GetScope() string {
func (oa *OAuth2ReqAuth) GetScope() string {
if oa.Scope == nil {
return ""
}
return *oa.Scope
}

func (oa OAuth2ReqAuth) GetTokenEndpoint() (string, error) {
func (oa *OAuth2ReqAuth) GetTokenEndpoint() (string, error) {
return oa.TokenEndpoint, nil
}

func (oa OAuth2ReqAuth) GetTokenEndpointAuthMethod() *string {
func (oa *OAuth2ReqAuth) GetTokenEndpointAuthMethod() *string {
return oa.TokenEndpointAuthMethod
}
12 changes: 11 additions & 1 deletion config/runtime/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/avenga/couper/backend"
"github.com/avenga/couper/cache"
"github.com/avenga/couper/config"
"github.com/avenga/couper/errors"
"github.com/avenga/couper/eval"
"github.com/avenga/couper/handler/transport"
"github.com/avenga/couper/handler/validation"
Expand Down Expand Up @@ -122,7 +123,16 @@ func newAuthBackend(evalCtx *hcl.EvalContext, beConf *config.Backend, blocks hcl
return nil, diags
}

innerBackend := innerContent.Blocks.OfType("backend")[0] // backend block is set by configload
backendBlocks := innerContent.Blocks.OfType("backend")
if len(backendBlocks) == 0 {
r := beConf.OAuth2.Remain.MissingItemRange()
diag := &hcl.Diagnostics{&hcl.Diagnostic{
Subject: &r,
Summary: "missing backend initialization",
}}
return nil, errors.Configuration.Label("unexpected").With(diag)
}
innerBackend := backendBlocks[0] // backend block is set by configload
authBackend, authErr := NewBackend(evalCtx, innerBackend.Body, log, conf, memStore)
if authErr != nil {
return nil, authErr
Expand Down
40 changes: 5 additions & 35 deletions config/runtime/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/avenga/couper/cache"
"github.com/avenga/couper/config"
"github.com/avenga/couper/config/runtime/server"
"github.com/avenga/couper/config/sequence"
"github.com/avenga/couper/errors"
"github.com/avenga/couper/eval"
"github.com/avenga/couper/handler"
Expand Down Expand Up @@ -193,16 +194,9 @@ func newEndpointOptions(confCtx *hcl.EvalContext, endpointConf *config.Endpoint,
// newSequences lookups any request related dependency and sort them into a sequence.
// Also return left-overs for parallel usage.
func newSequences(proxies map[string]*producer.Proxy, requests map[string]*producer.Request,
items ...*config.Sequence) (producer.Sequences, producer.Requests, producer.Proxies) {

// just collect for filtering
var allDeps [][]string
for _, item := range items {
deps := make([]string, 0)
seen := make([]string, 0)
resolveSequence(item, &deps, &seen)
allDeps = append(allDeps, deps)
}
items ...*sequence.Item) (producer.Sequences, producer.Requests, producer.Proxies) {

allDeps := sequence.Dependencies(items)

var reqs producer.Requests
var ps producer.Proxies
Expand Down Expand Up @@ -240,7 +234,7 @@ reqLeftovers:
return seqs, reqs, ps
}

func newSequence(seq *config.Sequence,
func newSequence(seq *sequence.Item,
proxies map[string]*producer.Proxy,
requests map[string]*producer.Request) producer.Roundtrip {

Expand Down Expand Up @@ -290,27 +284,3 @@ func newSequenceItem(name, previous string,
}
return nil
}

func resolveSequence(item *config.Sequence, resolved, seen *[]string) {
name := item.Name
*seen = append(*seen, name)
for _, dep := range item.Deps() {
if !containsString(resolved, dep.Name) {
if !containsString(seen, dep.Name) {
resolveSequence(dep, resolved, seen)
continue
}
}
}

*resolved = append(*resolved, name)
}

func containsString(slice *[]string, needle string) bool {
for _, n := range *slice {
if n == needle {
return true
}
}
return false
}
Loading

0 comments on commit ff40871

Please sign in to comment.