Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix backend init #524

Merged
merged 8 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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