diff --git a/config/core/300-resources/route.yaml b/config/core/300-resources/route.yaml
index 725bb4cc0928..5c98afd97215 100644
--- a/config/core/300-resources/route.yaml
+++ b/config/core/300-resources/route.yaml
@@ -93,6 +93,13 @@ spec:
description: TrafficTarget holds a single entry of the routing table for a Route.
type: object
properties:
+ appendHeaders:
+ description: |-
+ AppendHeaders allow specifying additional HTTP headers to add
+ before forwarding a request to the destination service.
+ type: object
+ additionalProperties:
+ type: string
configurationName:
description: |-
ConfigurationName of a configuration to whose latest revision we will send
@@ -219,6 +226,13 @@ spec:
description: TrafficTarget holds a single entry of the routing table for a Route.
type: object
properties:
+ appendHeaders:
+ description: |-
+ AppendHeaders allow specifying additional HTTP headers to add
+ before forwarding a request to the destination service.
+ type: object
+ additionalProperties:
+ type: string
configurationName:
description: |-
ConfigurationName of a configuration to whose latest revision we will send
diff --git a/config/core/300-resources/service.yaml b/config/core/300-resources/service.yaml
index e2b14b4fbbed..627420799ca8 100644
--- a/config/core/300-resources/service.yaml
+++ b/config/core/300-resources/service.yaml
@@ -1520,6 +1520,13 @@ spec:
description: TrafficTarget holds a single entry of the routing table for a Route.
type: object
properties:
+ appendHeaders:
+ description: |-
+ AppendHeaders allow specifying additional HTTP headers to add
+ before forwarding a request to the destination service.
+ type: object
+ additionalProperties:
+ type: string
configurationName:
description: |-
ConfigurationName of a configuration to whose latest revision we will send
@@ -1656,6 +1663,13 @@ spec:
description: TrafficTarget holds a single entry of the routing table for a Route.
type: object
properties:
+ appendHeaders:
+ description: |-
+ AppendHeaders allow specifying additional HTTP headers to add
+ before forwarding a request to the destination service.
+ type: object
+ additionalProperties:
+ type: string
configurationName:
description: |-
ConfigurationName of a configuration to whose latest revision we will send
diff --git a/docs/serving-api.md b/docs/serving-api.md
index 77aad5ea6a63..6609f0485fdc 100644
--- a/docs/serving-api.md
+++ b/docs/serving-api.md
@@ -2082,6 +2082,19 @@ status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and
a hostname, but may not contain anything else (e.g. basic auth, url path, etc.)
+
+
+appendHeaders
+
+map[string]string
+
+ |
+
+(Optional)
+ AppendHeaders allow specifying additional HTTP headers to add
+before forwarding a request to the destination service.
+ |
+
diff --git a/pkg/apis/serving/v1/route_types.go b/pkg/apis/serving/v1/route_types.go
index a07112c87ee1..cad7b5a3188d 100644
--- a/pkg/apis/serving/v1/route_types.go
+++ b/pkg/apis/serving/v1/route_types.go
@@ -108,6 +108,12 @@ type TrafficTarget struct {
// a hostname, but may not contain anything else (e.g. basic auth, url path, etc.)
// +optional
URL *apis.URL `json:"url,omitempty"`
+
+ // AppendHeaders allow specifying additional HTTP headers to add
+ // before forwarding a request to the destination service.
+ //
+ // +optional
+ AppendHeaders map[string]string `json:"appendHeaders,omitempty"`
}
// RouteSpec holds the desired state of the Route (from the client).
diff --git a/pkg/apis/serving/v1/route_validation.go b/pkg/apis/serving/v1/route_validation.go
index a30b047fa88e..7b4b0bbe79c8 100644
--- a/pkg/apis/serving/v1/route_validation.go
+++ b/pkg/apis/serving/v1/route_validation.go
@@ -101,7 +101,8 @@ func (tt *TrafficTarget) Validate(ctx context.Context) *apis.FieldError {
errs := tt.validateLatestRevision(ctx)
errs = tt.validateRevisionAndConfiguration(ctx, errs)
errs = tt.validateTrafficPercentage(errs)
- return tt.validateURL(ctx, errs)
+ errs = tt.validateURL(ctx, errs)
+ return tt.validateAppendHeaders(ctx, errs)
}
func (tt *TrafficTarget) validateRevisionAndConfiguration(ctx context.Context, errs *apis.FieldError) *apis.FieldError {
@@ -191,6 +192,24 @@ func (tt *TrafficTarget) validateURL(ctx context.Context, errs *apis.FieldError)
return errs
}
+func (tt *TrafficTarget) validateAppendHeaders(ctx context.Context, errs *apis.FieldError) *apis.FieldError {
+ if len(tt.AppendHeaders) == 0 {
+ return errs
+ }
+
+ // AppendHeaders is not allowed in status.
+ if apis.IsInStatus(ctx) {
+ return errs.Also(apis.ErrDisallowedFields("appendHeaders"))
+ }
+
+ for key := range tt.AppendHeaders {
+ if len(validation.IsHTTPHeaderName(key)) > 0 {
+ errs = errs.Also(apis.ErrInvalidKeyName(key, "appendHeaders"))
+ }
+ }
+ return errs
+}
+
// validateLabels function validates route labels.
func (r *Route) validateLabels() (errs *apis.FieldError) {
if val, ok := r.Labels[serving.ServiceLabelKey]; ok {
diff --git a/pkg/apis/serving/v1/route_validation_test.go b/pkg/apis/serving/v1/route_validation_test.go
index 50b3a62cf98a..3246db68b037 100644
--- a/pkg/apis/serving/v1/route_validation_test.go
+++ b/pkg/apis/serving/v1/route_validation_test.go
@@ -255,6 +255,38 @@ func TestTrafficTargetValidation(t *testing.T) {
},
wc: apis.WithinSpec,
want: apis.ErrDisallowedFields("url"),
+ }, {
+ name: "valid append headers",
+ tt: &TrafficTarget{
+ ConfigurationName: "foo",
+ Percent: ptr.Int64(100),
+ AppendHeaders: map[string]string{
+ "foo": "bar",
+ },
+ },
+ wc: apis.WithinSpec,
+ }, {
+ name: "invalid append headers (status)",
+ tt: &TrafficTarget{
+ RevisionName: "v1",
+ Percent: ptr.Int64(100),
+ AppendHeaders: map[string]string{
+ "foo": "bar",
+ },
+ },
+ wc: apis.WithinStatus,
+ want: apis.ErrDisallowedFields("appendHeaders"),
+ }, {
+ name: "invalid append headers (invalid key)",
+ tt: &TrafficTarget{
+ ConfigurationName: "foo",
+ Percent: ptr.Int64(100),
+ AppendHeaders: map[string]string{
+ "foo/bar": "bar",
+ },
+ },
+ wc: apis.WithinSpec,
+ want: apis.ErrInvalidKeyName("foo/bar", "appendHeaders"),
}}
for _, test := range tests {
diff --git a/pkg/apis/serving/v1/zz_generated.deepcopy.go b/pkg/apis/serving/v1/zz_generated.deepcopy.go
index c42bb4b927c7..576cd7b9e9a0 100644
--- a/pkg/apis/serving/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/serving/v1/zz_generated.deepcopy.go
@@ -559,6 +559,13 @@ func (in *TrafficTarget) DeepCopyInto(out *TrafficTarget) {
*out = new(apis.URL)
(*in).DeepCopyInto(*out)
}
+ if in.AppendHeaders != nil {
+ in, out := &in.AppendHeaders, &out.AppendHeaders
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
return
}
diff --git a/pkg/reconciler/route/resources/ingress.go b/pkg/reconciler/route/resources/ingress.go
index ba593ebe421d..ce24020d4ad3 100644
--- a/pkg/reconciler/route/resources/ingress.go
+++ b/pkg/reconciler/route/resources/ingress.go
@@ -322,10 +322,10 @@ func makeBaseIngressPath(ns string, targets traffic.RevisionTargets,
ServicePort: servicePort,
},
Percent: int(*t.Percent),
- AppendHeaders: map[string]string{
+ AppendHeaders: kmeta.UnionMaps(map[string]string{
activator.RevisionHeaderName: t.TrafficTarget.RevisionName,
activator.RevisionHeaderNamespace: ns,
- },
+ }, t.TrafficTarget.AppendHeaders),
})
} else {
for i := range cfg.Revisions {
@@ -334,15 +334,13 @@ func makeBaseIngressPath(ns string, targets traffic.RevisionTargets,
IngressBackend: netv1alpha1.IngressBackend{
ServiceNamespace: ns,
ServiceName: rev.RevisionName,
- // Port on the public service must match port on the activator.
- // Otherwise, the serverless services can't guarantee seamless positive handoff.
- ServicePort: servicePort,
+ ServicePort: servicePort,
},
Percent: rev.Percent,
- AppendHeaders: map[string]string{
+ AppendHeaders: kmeta.UnionMaps(map[string]string{
activator.RevisionHeaderName: rev.RevisionName,
activator.RevisionHeaderNamespace: ns,
- },
+ }, t.TrafficTarget.AppendHeaders),
})
}
}
diff --git a/pkg/reconciler/route/resources/ingress_test.go b/pkg/reconciler/route/resources/ingress_test.go
index 2bfd12db0ff8..c7dacff2f51f 100644
--- a/pkg/reconciler/route/resources/ingress_test.go
+++ b/pkg/reconciler/route/resources/ingress_test.go
@@ -679,6 +679,82 @@ func TestMakeIngressSpecCorrectRuleVisibility(t *testing.T) {
}
}
+func TestMakeIngressSpecWithAppendHeaders(t *testing.T) {
+ targets := map[string]traffic.RevisionTargets{
+ traffic.DefaultTarget: {{
+ TrafficTarget: v1.TrafficTarget{
+ ConfigurationName: "config",
+ RevisionName: "v2",
+ Percent: ptr.Int64(100),
+ AppendHeaders: map[string]string{
+ "Foo": "Bar",
+ },
+ },
+ }},
+ }
+
+ r := Route(ns, "test-route", WithURL)
+
+ expected := []netv1alpha1.IngressRule{{
+ Hosts: []string{
+ "test-route." + ns,
+ "test-route." + ns + ".svc",
+ pkgnet.GetServiceHostname("test-route", ns),
+ },
+ HTTP: &netv1alpha1.HTTPIngressRuleValue{
+ Paths: []netv1alpha1.HTTPIngressPath{{
+ Splits: []netv1alpha1.IngressBackendSplit{{
+ IngressBackend: netv1alpha1.IngressBackend{
+ ServiceNamespace: ns,
+ ServiceName: "v2",
+ ServicePort: intstr.FromInt(80),
+ },
+ Percent: 100,
+ AppendHeaders: map[string]string{
+ "Knative-Serving-Revision": "v2",
+ "Knative-Serving-Namespace": ns,
+ "Foo": "Bar",
+ },
+ }},
+ }},
+ },
+ Visibility: netv1alpha1.IngressVisibilityClusterLocal,
+ }, {
+ Hosts: []string{
+ "test-route." + ns + ".example.com",
+ },
+ HTTP: &netv1alpha1.HTTPIngressRuleValue{
+ Paths: []netv1alpha1.HTTPIngressPath{{
+ Splits: []netv1alpha1.IngressBackendSplit{{
+ IngressBackend: netv1alpha1.IngressBackend{
+ ServiceNamespace: ns,
+ ServiceName: "v2",
+ ServicePort: intstr.FromInt(80),
+ },
+ Percent: 100,
+ AppendHeaders: map[string]string{
+ "Knative-Serving-Revision": "v2",
+ "Knative-Serving-Namespace": ns,
+ "Foo": "Bar",
+ },
+ }},
+ }},
+ },
+ Visibility: netv1alpha1.IngressVisibilityExternalIP,
+ }}
+
+ tc := &traffic.Config{Targets: targets}
+ ro := tc.BuildRollout()
+ ci, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro)
+ if err != nil {
+ t.Error("Unexpected error", err)
+ }
+
+ if !cmp.Equal(expected, ci.Rules) {
+ t.Error("Unexpected rules (-want, +got):", cmp.Diff(expected, ci.Rules))
+ }
+}
+
func TestMakeIngressSpecCorrectRulesWithTagBasedRouting(t *testing.T) {
targets := map[string]traffic.RevisionTargets{
traffic.DefaultTarget: {{