github.com/avenga/couper@v1.12.2/config/configload/helper.go (about)

     1  package configload
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/gohcl"
     9  	"github.com/hashicorp/hcl/v2/hclsyntax"
    10  
    11  	"github.com/avenga/couper/config"
    12  	hclbody "github.com/avenga/couper/config/body"
    13  	"github.com/avenga/couper/config/sequence"
    14  	"github.com/avenga/couper/errors"
    15  )
    16  
    17  type helper struct {
    18  	config       *config.Couper
    19  	context      *hcl.EvalContext
    20  	content      *hcl.BodyContent
    21  	defsBackends map[string]*hclsyntax.Body
    22  }
    23  
    24  // newHelper creates a container with some methods to keep things simple here and there.
    25  func newHelper(body hcl.Body) (*helper, error) {
    26  	couperConfig := &config.Couper{
    27  		Context:     evalContext,
    28  		Definitions: &config.Definitions{},
    29  		Defaults:    defaultsConfig,
    30  		Settings:    config.NewDefaultSettings(),
    31  	}
    32  
    33  	schema, _ := gohcl.ImpliedBodySchema(couperConfig)
    34  	content, diags := body.Content(schema)
    35  	if content == nil { // reference diags only for missing content, due to optional server label
    36  		return nil, fmt.Errorf("invalid configuration: %w", diags)
    37  	}
    38  
    39  	return &helper{
    40  		config:       couperConfig,
    41  		content:      content,
    42  		context:      evalContext.HCLContext(),
    43  		defsBackends: make(map[string]*hclsyntax.Body),
    44  	}, nil
    45  }
    46  
    47  func (h *helper) addBackend(block *hcl.Block) {
    48  	name := block.Labels[0]
    49  
    50  	backendBody := block.Body.(*hclsyntax.Body)
    51  	setName(name, backendBody)
    52  
    53  	h.defsBackends[name] = backendBody
    54  }
    55  
    56  func (h *helper) configureDefinedBackends() error {
    57  	backendNames, err := h.resolveBackendDeps()
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	for _, name := range backendNames {
    63  		b, set := h.defsBackends[name]
    64  		if !set {
    65  			return errors.Configuration.Messagef("referenced backend %q is not defined", name)
    66  		}
    67  		be, err := PrepareBackend(h, "_init", "", &config.Backend{Name: name, Remain: b})
    68  		if err != nil {
    69  			return err
    70  		}
    71  		h.config.Definitions.Backend = append(
    72  			h.config.Definitions.Backend,
    73  			&config.Backend{Remain: be, Name: name},
    74  		)
    75  
    76  		h.defsBackends[name] = be
    77  	}
    78  	return err
    79  }
    80  
    81  func (h *helper) configureACBackends() error {
    82  	var acs []config.BackendInitialization
    83  	for _, ac := range h.config.Definitions.JWT {
    84  		acs = append(acs, ac)
    85  	}
    86  	for _, ac := range h.config.Definitions.OAuth2AC {
    87  		acs = append(acs, ac)
    88  	}
    89  
    90  	for _, ac := range h.config.Definitions.OIDC {
    91  		acs = append(acs, ac)
    92  	}
    93  
    94  	for _, ac := range acs {
    95  		if err := ac.Prepare(func(attr string, attrVal string, b config.Body) (*hclsyntax.Body, error) {
    96  			return PrepareBackend(h, attr, attrVal, b) // wrap helper
    97  		}); err != nil {
    98  			return err
    99  		}
   100  	}
   101  	return nil
   102  }
   103  
   104  // resolveBackendDeps returns defined backends ordered by reference. Referenced ones need to be configured first.
   105  func (h *helper) resolveBackendDeps() (uniqueItems []string, err error) {
   106  	// collect referenced backends
   107  	refs := make(map[string][]string)
   108  	h.collectBackendDeps(refs)
   109  	// built up deps
   110  	refPtr := map[string]*sequence.Item{}
   111  	for name := range refs {
   112  		parent := sequence.NewBackendItem(name)
   113  		refPtr[name] = parent
   114  	}
   115  
   116  	defer func() {
   117  		if p := recover(); p != nil { // since we use sequence related logic, replace wording due to backend context here
   118  			err = errors.Configuration.Message(strings.Replace(fmt.Sprintf("%s", p), "sequence ", "", 1))
   119  		}
   120  	}()
   121  
   122  	var defs sequence.List
   123  	for parent, ref := range refs {
   124  		for _, r := range ref {
   125  			p := refPtr[parent]
   126  			if be, exist := refPtr[r]; exist {
   127  				p.Add(be)
   128  			} else {
   129  				p.Add(sequence.NewBackendItem(r))
   130  			}
   131  			defs = append(defs, p)
   132  		}
   133  	}
   134  
   135  	items := sequence.Dependencies(defs)
   136  
   137  	// do not forget the other ones
   138  	var standalone []string
   139  	for def := range h.defsBackends {
   140  		standalone = append(standalone, def)
   141  	}
   142  	items = append(items, standalone)
   143  
   144  	// unique by name /w score (sort?) // TODO: MAY refine with scoring of appearance
   145  	unique := make(map[string]int)
   146  	for _, seqItem := range items {
   147  		for _, name := range seqItem {
   148  			if _, exist := unique[name]; !exist {
   149  				unique[name] = 1
   150  				uniqueItems = append(uniqueItems, name)
   151  			} else {
   152  				unique[name]++
   153  			}
   154  		}
   155  	}
   156  
   157  	return uniqueItems, err
   158  }
   159  
   160  func (h *helper) collectBackendDeps(refs map[string][]string) {
   161  	for name, b := range h.defsBackends {
   162  		refs[name] = nil
   163  		oaBlocks := hclbody.BlocksOfType(b, oauth2)
   164  		h.collectFromBlocks(oaBlocks, name, refs)
   165  		trBlocks := hclbody.BlocksOfType(b, tokenRequest)
   166  		h.collectFromBlocks(trBlocks, name, refs)
   167  	}
   168  }
   169  
   170  func (h *helper) collectFromBlocks(authorizerBlocks hclsyntax.Blocks, name string, refs map[string][]string) {
   171  	for _, ab := range authorizerBlocks {
   172  		for _, be := range ab.Body.Attributes {
   173  			if be.Name == backend {
   174  				val, _ := be.Expr.Value(envContext)
   175  				refs[name] = append(refs[name], val.AsString())
   176  				break
   177  			}
   178  		}
   179  
   180  		for _, block := range ab.Body.Blocks {
   181  			if block.Type != backend {
   182  				continue
   183  			}
   184  			if len(block.Labels) > 0 {
   185  				refs[name] = append(refs[name], block.Labels[0])
   186  			}
   187  
   188  			for _, subBlock := range block.Body.Blocks {
   189  				switch subBlock.Type {
   190  				case oauth2, tokenRequest:
   191  					h.collectBackendDeps(refs)
   192  				}
   193  			}
   194  		}
   195  	}
   196  }