Skip to content

Commit

Permalink
feat(routes) add annotations for headers
Browse files Browse the repository at this point in the history
Add annotation support for headers. The annotation is in the form
"konghq.com/headers/example: value1,value2", where the portion of the
annoation name after "konghq.com/headers" ("example") is the
header name and the value is a CSV of header values.
  • Loading branch information
rainest committed Nov 2, 2022
1 parent 29aa2fb commit e16bca7
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 0 deletions.
20 changes: 20 additions & 0 deletions internal/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const (
WriteTimeoutKey = "/write-timeout"
ReadTimeoutKey = "/read-timeout"
RetriesKey = "/retries"
HeadersKey = "/headers"

// GatewayClassUnmanagedAnnotationSuffix is an annotation used on a Gateway resource to
// indicate that the GatewayClass should be reconciled according to unmanaged
Expand Down Expand Up @@ -293,6 +294,25 @@ func ExtractRetries(anns map[string]string) (string, bool) {
return val, true
}

// ExtractHeaders extracts the parsed headerr annotations values. It returns a map of header names to slices of values.
func ExtractHeaders(anns map[string]string) (map[string][]string, bool) {
headers := make(map[string][]string)
prefix := AnnotationPrefix + HeadersKey + "/"
for key, val := range anns {
if strings.HasPrefix(key, prefix) {
header := strings.TrimPrefix(key, prefix)
if len(header) == 0 || len(val) == 0 {
continue
}
headers[header] = strings.Split(val, ",")
}
}
if len(headers) == 0 {
return headers, false
}
return headers, true
}

// ExtractUnmanagedGatewayClassMode extracts the value of the unmanaged gateway
// mode annotation.
func ExtractUnmanagedGatewayClassMode(anns map[string]string) string {
Expand Down
58 changes: 58 additions & 0 deletions internal/annotations/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,3 +836,61 @@ func TestExtractRetries(t *testing.T) {
})
}
}

func TestExtractHeaders(t *testing.T) {
type args struct {
anns map[string]string
}
tests := []struct {
name string
args args
want map[string][]string
}{
{
name: "empty",
want: map[string][]string{},
},
{
name: "non-empty",
args: args{
anns: map[string]string{
"konghq.com/headers/foo": "foo",
},
},
want: map[string][]string{"foo": {"foo"}},
},
{
name: "no separator",
args: args{
anns: map[string]string{
"konghq.com/headersfoo": "foo",
},
},
want: map[string][]string{},
},
{
name: "no header name",
args: args{
anns: map[string]string{
"konghq.com/headers/": "foo",
},
},
want: map[string][]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := ExtractHeaders(tt.args.anns)
if len(tt.want) == 0 {
assert.False(t, ok)
} else {
assert.True(t, ok)
}
for key, val := range tt.want {
actual, ok := got[key]
assert.True(t, ok)
assert.Equal(t, val, actual)
}
})
}
}
9 changes: 9 additions & 0 deletions internal/dataplane/kongstate/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func (r *Route) overrideByAnnotation(log logrus.FieldLogger) {
r.overrideRequestBuffering(log, r.Ingress.Annotations)
r.overrideResponseBuffering(log, r.Ingress.Annotations)
r.overrideHosts(log, r.Ingress.Annotations)
r.overrideHeaders(r.Ingress.Annotations)
}

// override sets Route fields by KongIngress first, then by annotation.
Expand Down Expand Up @@ -405,3 +406,11 @@ func (r *Route) overrideHosts(log logrus.FieldLogger, anns map[string]string) {

r.Hosts = hosts
}

func (r *Route) overrideHeaders(anns map[string]string) {
headers, exists := annotations.ExtractHeaders(anns)
if !exists {
return
}
r.Headers = headers
}
82 changes: 82 additions & 0 deletions internal/dataplane/kongstate/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,3 +1055,85 @@ func TestOverrideHosts(t *testing.T) {
})
}
}

func TestOverrideHeaders(t *testing.T) {
type args struct {
route Route
anns map[string]string
}
tests := []struct {
name string
args args
want Route
}{
{name: "basic empty route"},
{
name: "single header single value",
args: args{
anns: map[string]string{
"konghq.com/headers/x-example": "example",
},
},
want: Route{
Route: kong.Route{
Headers: map[string][]string{"x-example": {"example"}},
},
},
},
{
name: "single header multi value",
args: args{
anns: map[string]string{
"konghq.com/headers/x-example": "foo,bar",
},
},
want: Route{
Route: kong.Route{
Headers: map[string][]string{"x-example": {"foo", "bar"}},
},
},
},
{
name: "multi header single value",
args: args{
anns: map[string]string{
"konghq.com/headers/x-foo": "example",
"konghq.com/headers/x-bar": "example",
},
},
want: Route{
Route: kong.Route{
Headers: map[string][]string{
"x-foo": {"example"},
"x-bar": {"example"},
},
},
},
},
{
name: "multi header multi value",
args: args{
anns: map[string]string{
"konghq.com/headers/x-foo": "foo,bar",
"konghq.com/headers/x-bar": "bar,baz",
},
},
want: Route{
Route: kong.Route{
Headers: map[string][]string{
"x-foo": {"foo", "bar"},
"x-bar": {"bar", "baz"},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.route.overrideHeaders(tt.args.anns)
if !reflect.DeepEqual(tt.args.route, tt.want) {
t.Errorf("overrideHeaders() got = %v, want %v", tt.args.route, tt.want)
}
})
}
}

0 comments on commit e16bca7

Please sign in to comment.