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 URL vs. backend.origin #144

Merged
merged 7 commits into from
Mar 15, 2021
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
128 changes: 61 additions & 67 deletions config/configload/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/avenga/couper/config/parser"
"github.com/avenga/couper/config/startup"
"github.com/avenga/couper/eval"
"github.com/avenga/couper/internal/seetie"
)

const (
Expand Down Expand Up @@ -305,19 +306,10 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints) error

proxyConfig.Remain = proxyBlock.Body

createFromURL, err := shouldCreateFromURL(proxyConfig.URL, proxyConfig.BackendName, proxyBlock)
var err error
proxyConfig.Backend, err = newBackend(definedBackends, proxyConfig, proxyConfig.URL)
if err != nil {
return err
} else if createFromURL {
proxyConfig.Backend, err = newBackendFromURL(proxyConfig.URL)
if err != nil {
return err
}
} else {
proxyConfig.Backend, err = newBackend(definedBackends, proxyConfig)
if err != nil {
return err
}
}

endpoint.Proxies = append(endpoint.Proxies, proxyConfig)
Expand All @@ -338,19 +330,10 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints) error

reqConfig.Remain = reqBlock.Body

createFromURL, err := shouldCreateFromURL(reqConfig.URL, reqConfig.BackendName, reqBlock)
var err error
reqConfig.Backend, err = newBackend(definedBackends, reqConfig, reqConfig.URL)
if err != nil {
return err
} else if createFromURL {
reqConfig.Backend, err = newBackendFromURL(reqConfig.URL)
if err != nil {
return err
}
} else {
reqConfig.Backend, err = newBackend(definedBackends, reqConfig)
if err != nil {
return err
}
}

endpoint.Requests = append(endpoint.Requests, reqConfig)
Expand Down Expand Up @@ -399,41 +382,6 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints) error
return nil
}

// shouldCreateFromURL determines some option and reads a possible backend block from body.
// Since its still valid to override parent backend with a "local" url.
func shouldCreateFromURL(url, backendName string, block *hcl.Block) (bool, error) {
if url == "" {
return false, nil
}

if url != "" && backendName != "" {
return false, hcl.Diagnostics{&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "either url or backend is allowed",
Subject: &block.DefRange,
}}
}

content, err := contentByType(backend, block.Body)
if err != nil {
return false, err
}

if content == nil {
return true, nil
}

if len(content.Blocks.OfType(backend)) > 0 {
return false, hcl.Diagnostics{&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "either url or backend is allowed",
Subject: &block.DefRange,
}}
}

return true, nil
}

func validLabelName(name string, hr *hcl.Range) error {
if !regexProxyRequestLabel.MatchString(name) {
return hcl.Diagnostics{&hcl.Diagnostic{
Expand Down Expand Up @@ -485,12 +433,48 @@ func contentByType(blockType string, body hcl.Body) (*hcl.BodyContent, error) {
return content, nil
}

func newBackend(definedBackends Backends, inlineConfig config.Inline) (hcl.Body, error) {
func newBackend(definedBackends Backends, inlineConfig config.Inline, url string) (hcl.Body, error) {
bend, err := mergeBackendBodies(definedBackends, inlineConfig)
if err != nil {
return nil, err
}

if url != "" {
body, urlOrigin, err := newBackendFromURL(url)
if err != nil {
return nil, err
}

if bend == nil {
bend = body
} else {
content, _, diags := bend.PartialContent(
&hcl.BodySchema{Attributes: []hcl.AttributeSchema{{Name: "origin"}}},
)
if diags.HasErrors() {
return nil, diags
}

if attr, ok := content.Attributes["origin"]; ok {
val, err := attr.Expr.Value(envContext)
if err != nil {
return nil, err
}
beOrigin := seetie.ValueToString(val)

if urlOrigin != beOrigin {
r := inlineConfig.HCLBody().MissingItemRange()
return nil, hcl.Diagnostics{&hcl.Diagnostic{
Subject: &r,
Summary: "The origin of 'url' and 'backend.origin' must be equal",
}}
}
}

bend = MergeBodies([]hcl.Body{bend, body})
}
}

if err = validateOrigin(bend); err != nil {
r := inlineConfig.HCLBody().MissingItemRange()
return nil, hcl.Diagnostics{&hcl.Diagnostic{
Expand All @@ -502,20 +486,30 @@ func newBackend(definedBackends Backends, inlineConfig config.Inline) (hcl.Body,
return bend, nil
}

func newBackendFromURL(rawURL string) (hcl.Body, error) {
func newBackendFromURL(rawURL string) (hcl.Body, string, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
return nil, "", err
}

origin := u.Scheme + "://" + u.Host

attributes := map[string]*hcl.Attribute{
"name": {Name: "name", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(defaultNameLabel)}},
"origin": {Name: "origin", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(origin)}},
"path": {Name: "path", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(u.Path)}},
}

if len(u.Query()) > 0 {
attributes["set_query_params"] = &hcl.Attribute{
Name: "set_query_params",
Expr: &hclsyntax.LiteralValueExpr{Val: seetie.ValuesMapToValue(u.Query())},
}
}

return hclbody.New(&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {Name: "name", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(defaultNameLabel)}},
"origin": {Name: "origin", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(u.Scheme + "://" + u.Host)}},
"path": {Name: "path", Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(u.RawPath)}},
// TODO: set query_params (request) -vs- set_query_params (proxy)
},
}), nil
Attributes: attributes,
}), origin, nil
}

// validateOrigin checks at least for an origin attribute definition.
Expand Down
82 changes: 82 additions & 0 deletions server/http_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,88 @@ func TestHTTPServer_Backends(t *testing.T) {
}
}

func TestHTTPServer_OriginVsURL(t *testing.T) {
client := newClient()

configPath := "testdata/integration/url/"

type expectation struct {
Path string
Query url.Values
}

type testCase struct {
file string
exp expectation
}

for _, tc := range []testCase{
{"01_couper.hcl", expectation{
Path: "/anything",
Query: url.Values{
"x": []string{"y"},
},
}},
{"02_couper.hcl", expectation{
Path: "/anything",
Query: url.Values{
"a": []string{"A"},
},
}},
{"03_couper.hcl", expectation{
Path: "/anything",
Query: url.Values{
"x": []string{"y"},
},
}},
{"04_couper.hcl", expectation{
Path: "/anything",
Query: url.Values{
"x": []string{"y"},
},
}},
{"05_couper.hcl", expectation{
Path: "/anything",
Query: url.Values{
"x": []string{"y"},
},
}},
{"06_couper.hcl", expectation{
Path: "/anything",
Query: url.Values{
"x": []string{"y"},
},
}},
} {
t.Run("File "+tc.file, func(subT *testing.T) {
helper := test.New(subT)

shutdown, _ := newCouper(path.Join(configPath, tc.file), helper)
defer shutdown()

req, err := http.NewRequest(http.MethodGet, "http://example.com:8080", nil)
helper.Must(err)

res, err := client.Do(req)
helper.Must(err)

resBytes, err := ioutil.ReadAll(res.Body)
helper.Must(err)
res.Body.Close()

var jsonResult expectation
err = json.Unmarshal(resBytes, &jsonResult)
if err != nil {
t.Errorf("unmarshal json: %v: got:\n%s", err, string(resBytes))
}

if !reflect.DeepEqual(jsonResult, tc.exp) {
t.Errorf("\nwant: \n%#v\ngot: \n%#v", tc.exp, jsonResult)
}
})
}
}

func TestHTTPServer_TrailingSlash(t *testing.T) {
client := newClient()

Expand Down
9 changes: 9 additions & 0 deletions server/testdata/integration/url/01_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
server "url" {
api {
endpoint "/" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything?x=y"
}
}
}
}
18 changes: 18 additions & 0 deletions server/testdata/integration/url/02_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
server "url" {
api {
endpoint "/" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything"
backend = "test"
}
}
}
}

definitions {
backend "test" {
set_query_params = {
a = "A"
}
}
}
18 changes: 18 additions & 0 deletions server/testdata/integration/url/03_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
server "url" {
api {
endpoint "/" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything?x=y"
backend = "test"
}
}
}
}

definitions {
backend "test" {
set_query_params = {
a = "A"
}
}
}
20 changes: 20 additions & 0 deletions server/testdata/integration/url/04_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
server "url" {
api {
endpoint "/" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything?x=y"
backend = "anything"
}
}
}
}

definitions {
# backend origin within a definition block gets replaced with the integration test "anything" server.
backend "anything" {
origin = env.COUPER_TEST_BACKEND_ADDR
set_query_params = {
a = "A"
}
}
}
14 changes: 14 additions & 0 deletions server/testdata/integration/url/05_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
server "url" {
api {
endpoint "/" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything?x=y"
backend {
set_query_params = {
a = "A"
}
}
}
}
}
}
15 changes: 15 additions & 0 deletions server/testdata/integration/url/06_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server "url" {
api {
endpoint "/" {
proxy {
url = "${env.COUPER_TEST_BACKEND_ADDR}/anything?x=y"
backend {
origin = env.COUPER_TEST_BACKEND_ADDR
set_query_params = {
a = "A"
}
}
}
}
}
}