This is an automated email from the ASF dual-hosted git repository.

zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 7b3deb5e feat: add support for host pass in upstream crd (#1889)
7b3deb5e is described below

commit 7b3deb5e84d257f46ce5c513d72b22a24af1be67
Author: ikatlinsky <[email protected]>
AuthorDate: Sat Aug 26 16:23:33 2023 +0200

    feat: add support for host pass in upstream crd (#1889)
    
    Co-authored-by: Katlinsky, Ilya <[email protected]>
---
 docs/en/latest/references/apisix_upstream.md      |   2 +
 pkg/kube/apisix/apis/config/v2/types.go           |  10 ++
 pkg/providers/translation/apisix_upstream.go      |  21 +++
 pkg/providers/translation/apisix_upstream_test.go |  47 +++++++
 pkg/types/apisix/v1/types.go                      |  87 ++++++++-----
 samples/deploy/crd/v1/ApisixUpstream.yaml         |   9 ++
 test/e2e/suite-features/upstream_pass_host.go     | 151 ++++++++++++++++++++++
 7 files changed, 292 insertions(+), 35 deletions(-)

diff --git a/docs/en/latest/references/apisix_upstream.md 
b/docs/en/latest/references/apisix_upstream.md
index e9544298..3eefea27 100644
--- a/docs/en/latest/references/apisix_upstream.md
+++ b/docs/en/latest/references/apisix_upstream.md
@@ -83,3 +83,5 @@ See the 
[definition](https://github.com/apache/apisix-ingress-controller/blob/ma
 | discovery.serviceName                      | string            | Name of the 
upstream service.                                                               
                                                                                
                                                     |
 | discovery.type                             | string            | Types of 
Service Discovery, which indicates what registry in APISIX the discovery uses. 
Should match the entry in APISIX's config. Can refer to the 
[doc](https://apisix.apache.org/docs/apisix/discovery/)                         
                                                  |
 | discovery.args                             | object            | Args map 
for discovery-spcefic parameters. Also can refer to the 
[doc](https://apisix.apache.org/docs/apisix/discovery/)                         
                                                                                
|
+| passHost                                   | string            | Configures 
the host when the request is forwarded to the upstream. Can be one of pass, 
node or rewrite. Defaults to pass if not specified: pass - transparently passes 
the client's host to the Upstream, node - uses the host configured in the node 
of the Upstream, rewrite - uses the value configured in upstreamHost.
+| upstreamHost                               | string            | Specifies 
the host of the Upstream request. This is only valid if the passHost is set to 
rewrite.
diff --git a/pkg/kube/apisix/apis/config/v2/types.go 
b/pkg/kube/apisix/apis/config/v2/types.go
index 59569cae..3e2daf88 100644
--- a/pkg/kube/apisix/apis/config/v2/types.go
+++ b/pkg/kube/apisix/apis/config/v2/types.go
@@ -543,6 +543,16 @@ type ApisixUpstreamConfig struct {
        // +optional
        Subsets []ApisixUpstreamSubset `json:"subsets,omitempty" 
yaml:"subsets,omitempty"`
 
+       // Configures the host when the request is forwarded to the upstream.
+       // Can be one of pass, node or rewrite.
+       // +optional
+       PassHost string `json:"passHost,omitempty" yaml:"passHost,omitempty"`
+
+       // Specifies the host of the Upstream request. This is only valid if
+       // the pass_host is set to rewrite
+       // +optional
+       UpstreamHost string `json:"upstreamHost,omitempty" 
yaml:"upstreamHost,omitempty"`
+
        // Discovery is used to configure service discovery for upstream.
        // +optional
        Discovery *Discovery `json:"discovery,omitempty" 
yaml:"discovery,omitempty"`
diff --git a/pkg/providers/translation/apisix_upstream.go 
b/pkg/providers/translation/apisix_upstream.go
index 162dd984..44aca7ba 100644
--- a/pkg/providers/translation/apisix_upstream.go
+++ b/pkg/providers/translation/apisix_upstream.go
@@ -21,6 +21,11 @@ import (
        apisixv1 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
+type passHostConfig struct {
+       passHost     string
+       upstreamHost string
+}
+
 func (t *translator) TranslateUpstreamConfigV2(au 
*configv2.ApisixUpstreamConfig) (*apisixv1.Upstream, error) {
        ups := apisixv1.NewDefaultUpstream()
        if err := t.translateUpstreamScheme(au.Scheme, ups); err != nil {
@@ -38,6 +43,9 @@ func (t *translator) TranslateUpstreamConfigV2(au 
*configv2.ApisixUpstreamConfig
        if err := t.translateClientTLSV2(au.TLSSecret, ups); err != nil {
                return nil, err
        }
+       if err := t.translatePassHost(&passHostConfig{au.PassHost, 
au.UpstreamHost}, ups); err != nil {
+               return nil, err
+       }
        if err := t.translateUpstreamDiscovery(au.Discovery, ups); err != nil {
                return nil, err
        }
@@ -368,3 +376,16 @@ func (t *translator) 
translateUpstreamPassiveHealthCheckV2(config *configv2.Pass
        }
        return &passive, nil
 }
+
+func (t *translator) translatePassHost(ph *passHostConfig, ups 
*apisixv1.Upstream) error {
+       switch ph.passHost {
+       case "", apisixv1.PassHostPass, apisixv1.PassHostNode, 
apisixv1.PassHostRewrite:
+               ups.PassHost = ph.passHost
+       default:
+               return &TranslateError{Field: "passHost", Reason: "invalid 
value"}
+       }
+
+       ups.UpstreamHost = ph.upstreamHost
+
+       return nil
+}
diff --git a/pkg/providers/translation/apisix_upstream_test.go 
b/pkg/providers/translation/apisix_upstream_test.go
index 92d8081c..6723535e 100644
--- a/pkg/providers/translation/apisix_upstream_test.go
+++ b/pkg/providers/translation/apisix_upstream_test.go
@@ -409,3 +409,50 @@ func TestUpstreamRetriesAndTimeoutV2(t *testing.T) {
                Read:    15,
        }, ups.Timeout)
 }
+
+func TestUpstreamPassHost(t *testing.T) {
+       tr := &translator{}
+       tests := []struct {
+               name     string
+               phc      *passHostConfig
+               wantFunc func(t *testing.T, err error, ups *apisixv1.Upstream, 
phc *passHostConfig)
+       }{
+               {
+                       name: "should be empty when settings not set 
explicitly",
+                       phc:  &passHostConfig{},
+                       wantFunc: func(t *testing.T, err error, ups 
*apisixv1.Upstream, phc *passHostConfig) {
+                               assert.Nil(t, err)
+                               assert.Empty(t, ups.PassHost)
+                               assert.Empty(t, ups.UpstreamHost)
+                       },
+               },
+               {
+                       name: "should set passHost to pass",
+                       phc:  &passHostConfig{passHost: apisixv1.PassHostPass},
+                       wantFunc: func(t *testing.T, err error, ups 
*apisixv1.Upstream, phc *passHostConfig) {
+                               assert.Nil(t, err)
+                               assert.Equal(t, phc.passHost, ups.PassHost)
+                               assert.Empty(t, ups.UpstreamHost)
+                       },
+               },
+               {
+                       name: "should fail when passHost set to invalid value",
+                       phc:  &passHostConfig{passHost: "unknown"},
+                       wantFunc: func(t *testing.T, err error, ups 
*apisixv1.Upstream, phc *passHostConfig) {
+                               assert.Equal(t, &TranslateError{
+                                       Field:  "passHost",
+                                       Reason: "invalid value",
+                               }, err)
+                       },
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       ups := apisixv1.NewDefaultUpstream()
+                       err := tr.translatePassHost(tt.phc, ups)
+
+                       tt.wantFunc(t, err, ups, tt.phc)
+               })
+       }
+}
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 04ed3be5..d85c386d 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -79,6 +79,13 @@ const (
        // DefaultUpstreamTimeout represents the default connect,
        // read and send timeout (in seconds) with upstreams.
        DefaultUpstreamTimeout = 60
+
+       // PassHostPass represents pass option for pass_host Upstream settings.
+       PassHostPass = "pass"
+       // PassHostPass represents node option for pass_host Upstream settings.
+       PassHostNode = "node"
+       // PassHostPass represents rewrite option for pass_host Upstream 
settings.
+       PassHostRewrite = "rewrite"
 )
 
 var ValidSchemes map[string]struct{} = map[string]struct{}{
@@ -196,15 +203,17 @@ func (p *Plugins) DeepCopy() *Plugins {
 type Upstream struct {
        Metadata `json:",inline" yaml:",inline"`
 
-       Type    string               `json:"type,omitempty" 
yaml:"type,omitempty"`
-       HashOn  string               `json:"hash_on,omitempty" 
yaml:"hash_on,omitempty"`
-       Key     string               `json:"key,omitempty" yaml:"key,omitempty"`
-       Checks  *UpstreamHealthCheck `json:"checks,omitempty" 
yaml:"checks,omitempty"`
-       Nodes   UpstreamNodes        `json:"nodes" yaml:"nodes"`
-       Scheme  string               `json:"scheme,omitempty" 
yaml:"scheme,omitempty"`
-       Retries *int                 `json:"retries,omitempty" 
yaml:"retries,omitempty"`
-       Timeout *UpstreamTimeout     `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
-       TLS     *ClientTLS           `json:"tls,omitempty" yaml:"tls,omitempty"`
+       Type         string               `json:"type,omitempty" 
yaml:"type,omitempty"`
+       HashOn       string               `json:"hash_on,omitempty" 
yaml:"hash_on,omitempty"`
+       Key          string               `json:"key,omitempty" 
yaml:"key,omitempty"`
+       Checks       *UpstreamHealthCheck `json:"checks,omitempty" 
yaml:"checks,omitempty"`
+       Nodes        UpstreamNodes        `json:"nodes" yaml:"nodes"`
+       Scheme       string               `json:"scheme,omitempty" 
yaml:"scheme,omitempty"`
+       Retries      *int                 `json:"retries,omitempty" 
yaml:"retries,omitempty"`
+       Timeout      *UpstreamTimeout     `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
+       TLS          *ClientTLS           `json:"tls,omitempty" 
yaml:"tls,omitempty"`
+       PassHost     string               `json:"pass_host,omitempty" 
yaml:"pass_host,omitempty"`
+       UpstreamHost string               `json:"upstream_host,omitempty" 
yaml:"upstream_host,omitempty"`
 
        // for Service Discovery
        ServiceName   string            `json:"service_name,omitempty" 
yaml:"service_name,omitempty"`
@@ -271,10 +280,12 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
                        Key    string               `json:"key,omitempty" 
yaml:"key,omitempty"`
                        Checks *UpstreamHealthCheck `json:"checks,omitempty" 
yaml:"checks,omitempty"`
                        //Nodes   UpstreamNodes        `json:"nodes" 
yaml:"nodes"`
-                       Scheme  string           `json:"scheme,omitempty" 
yaml:"scheme,omitempty"`
-                       Retries *int             `json:"retries,omitempty" 
yaml:"retries,omitempty"`
-                       Timeout *UpstreamTimeout `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
-                       TLS     *ClientTLS       `json:"tls,omitempty" 
yaml:"tls,omitempty"`
+                       Scheme       string           `json:"scheme,omitempty" 
yaml:"scheme,omitempty"`
+                       Retries      *int             `json:"retries,omitempty" 
yaml:"retries,omitempty"`
+                       Timeout      *UpstreamTimeout `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
+                       HostPass     string           
`json:"pass_host,omitempty" yaml:"pass_host,omitempty"`
+                       UpstreamHost string           
`json:"upstream_host,omitempty" yaml:"upstream_host,omitempty"`
+                       TLS          *ClientTLS       `json:"tls,omitempty" 
yaml:"tls,omitempty"`
 
                        // for Service Discovery
                        ServiceName   string            
`json:"service_name,omitempty" yaml:"service_name,omitempty"`
@@ -288,10 +299,12 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
                        Key:    up.Key,
                        Checks: up.Checks,
                        //Nodes:   up.Nodes,
-                       Scheme:  up.Scheme,
-                       Retries: up.Retries,
-                       Timeout: up.Timeout,
-                       TLS:     up.TLS,
+                       Scheme:       up.Scheme,
+                       Retries:      up.Retries,
+                       Timeout:      up.Timeout,
+                       HostPass:     up.PassHost,
+                       UpstreamHost: up.UpstreamHost,
+                       TLS:          up.TLS,
 
                        ServiceName:   up.ServiceName,
                        DiscoveryType: up.DiscoveryType,
@@ -301,15 +314,17 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
                return json.Marshal(&struct {
                        Metadata `json:",inline" yaml:",inline"`
 
-                       Type    string               `json:"type,omitempty" 
yaml:"type,omitempty"`
-                       HashOn  string               `json:"hash_on,omitempty" 
yaml:"hash_on,omitempty"`
-                       Key     string               `json:"key,omitempty" 
yaml:"key,omitempty"`
-                       Checks  *UpstreamHealthCheck `json:"checks,omitempty" 
yaml:"checks,omitempty"`
-                       Nodes   UpstreamNodes        `json:"nodes" yaml:"nodes"`
-                       Scheme  string               `json:"scheme,omitempty" 
yaml:"scheme,omitempty"`
-                       Retries *int                 `json:"retries,omitempty" 
yaml:"retries,omitempty"`
-                       Timeout *UpstreamTimeout     `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
-                       TLS     *ClientTLS           `json:"tls,omitempty" 
yaml:"tls,omitempty"`
+                       Type         string               
`json:"type,omitempty" yaml:"type,omitempty"`
+                       HashOn       string               
`json:"hash_on,omitempty" yaml:"hash_on,omitempty"`
+                       Key          string               `json:"key,omitempty" 
yaml:"key,omitempty"`
+                       Checks       *UpstreamHealthCheck 
`json:"checks,omitempty" yaml:"checks,omitempty"`
+                       Nodes        UpstreamNodes        `json:"nodes" 
yaml:"nodes"`
+                       Scheme       string               
`json:"scheme,omitempty" yaml:"scheme,omitempty"`
+                       Retries      *int                 
`json:"retries,omitempty" yaml:"retries,omitempty"`
+                       Timeout      *UpstreamTimeout     
`json:"timeout,omitempty" yaml:"timeout,omitempty"`
+                       HostPass     string               
`json:"pass_host,omitempty" yaml:"pass_host,omitempty"`
+                       UpstreamHost string               
`json:"upstream_host,omitempty" yaml:"upstream_host,omitempty"`
+                       TLS          *ClientTLS           `json:"tls,omitempty" 
yaml:"tls,omitempty"`
 
                        // for Service Discovery
                        //ServiceName   string            
`json:"service_name,omitempty" yaml:"service_name,omitempty"`
@@ -318,15 +333,17 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
                }{
                        Metadata: up.Metadata,
 
-                       Type:    up.Type,
-                       HashOn:  up.HashOn,
-                       Key:     up.Key,
-                       Checks:  up.Checks,
-                       Nodes:   up.Nodes,
-                       Scheme:  up.Scheme,
-                       Retries: up.Retries,
-                       Timeout: up.Timeout,
-                       TLS:     up.TLS,
+                       Type:         up.Type,
+                       HashOn:       up.HashOn,
+                       Key:          up.Key,
+                       Checks:       up.Checks,
+                       Nodes:        up.Nodes,
+                       Scheme:       up.Scheme,
+                       Retries:      up.Retries,
+                       Timeout:      up.Timeout,
+                       HostPass:     up.PassHost,
+                       UpstreamHost: up.UpstreamHost,
+                       TLS:          up.TLS,
 
                        //ServiceName:   up.ServiceName,
                        //DiscoveryType: up.DiscoveryType,
diff --git a/samples/deploy/crd/v1/ApisixUpstream.yaml 
b/samples/deploy/crd/v1/ApisixUpstream.yaml
index 3bfcd5ac..ac0b67d6 100644
--- a/samples/deploy/crd/v1/ApisixUpstream.yaml
+++ b/samples/deploy/crd/v1/ApisixUpstream.yaml
@@ -516,6 +516,15 @@ spec:
                       type: string
                     send:
                       type: string
+                passHost:
+                  type: string
+                  enum:
+                    - pass
+                    - node
+                    - rewrite
+                upstreamHost:
+                  type: string
+                  pattern: "^\\*?[0-9a-zA-Z-._]+$"
                 tlsSecret:
                   description: ApisixSecret describes the Kubernetes Secret 
name and
                     namespace.
diff --git a/test/e2e/suite-features/upstream_pass_host.go 
b/test/e2e/suite-features/upstream_pass_host.go
new file mode 100644
index 00000000..37fd1216
--- /dev/null
+++ b/test/e2e/suite-features/upstream_pass_host.go
@@ -0,0 +1,151 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package features
+
+import (
+       "fmt"
+       "time"
+
+       ginkgo "github.com/onsi/ginkgo/v2"
+       "github.com/stretchr/testify/assert"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("suite-features: upstream pass host", func() {
+       suites := func(scaffoldFunc func() *scaffold.Scaffold) {
+               s := scaffoldFunc()
+
+               routeTpl := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+  name: httpbin-route
+spec:
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /*
+    backends:
+    - serviceName: %s
+      servicePort: %d
+`
+
+               ginkgo.It("is set to node", func() {
+                       backendSvc, backendPorts := s.DefaultHTTPBackend()
+                       ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+                       err := s.CreateVersionedApisixResource(ar)
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       time.Sleep(5 * time.Second)
+
+                       au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: %s
+spec:
+  passHost: node
+`, backendSvc)
+                       err = s.CreateVersionedApisixResource(au)
+                       assert.Nil(ginkgo.GinkgoT(), err, "create 
ApisixUpstream")
+                       time.Sleep(2 * time.Second)
+
+                       ups, err := s.ListApisixUpstreams()
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       assert.Len(ginkgo.GinkgoT(), ups, 1)
+                       assert.Equal(ginkgo.GinkgoT(), "node", ups[0].PassHost)
+               })
+
+               ginkgo.It("is set to rewrite with upstream host", func() {
+                       backendSvc, backendPorts := s.DefaultHTTPBackend()
+                       ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+                       err := s.CreateVersionedApisixResource(ar)
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       time.Sleep(5 * time.Second)
+
+                       au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: %s
+spec:
+  passHost: rewrite
+  upstreamHost: host
+`, backendSvc)
+                       err = s.CreateVersionedApisixResource(au)
+                       assert.Nil(ginkgo.GinkgoT(), err, "create 
ApisixUpstream")
+                       time.Sleep(2 * time.Second)
+
+                       ups, err := s.ListApisixUpstreams()
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       assert.Len(ginkgo.GinkgoT(), ups, 1)
+                       assert.Equal(ginkgo.GinkgoT(), "rewrite", 
ups[0].PassHost)
+                       assert.Equal(ginkgo.GinkgoT(), "host", 
ups[0].UpstreamHost)
+               })
+
+               ginkgo.It("is set to node with upstream host", func() {
+                       backendSvc, backendPorts := s.DefaultHTTPBackend()
+                       ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+                       err := s.CreateVersionedApisixResource(ar)
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       time.Sleep(5 * time.Second)
+
+                       au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: %s
+spec:
+  passHost: node
+  upstreamHost: host
+`, backendSvc)
+                       err = s.CreateVersionedApisixResource(au)
+                       assert.Nil(ginkgo.GinkgoT(), err, "create 
ApisixUpstream")
+                       time.Sleep(2 * time.Second)
+
+                       ups, err := s.ListApisixUpstreams()
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       assert.Len(ginkgo.GinkgoT(), ups, 1)
+                       assert.Equal(ginkgo.GinkgoT(), "node", ups[0].PassHost)
+                       assert.Equal(ginkgo.GinkgoT(), "host", 
ups[0].UpstreamHost)
+               })
+
+               ginkgo.It("is set to invalid value", func() {
+                       backendSvc, backendPorts := s.DefaultHTTPBackend()
+                       ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+                       err := s.CreateVersionedApisixResource(ar)
+                       assert.Nil(ginkgo.GinkgoT(), err)
+                       time.Sleep(5 * time.Second)
+
+                       au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: %s
+spec:
+  passHost: invalid
+`, backendSvc)
+                       err = s.CreateVersionedApisixResource(au)
+                       assert.NotNil(ginkgo.GinkgoT(), err)
+               })
+       }
+
+       ginkgo.Describe("suite-features: scaffold v2", func() {
+               suites(scaffold.NewDefaultV2Scaffold)
+       })
+})

Reply via email to